Merge branch 'develop' into fix/3579

This commit is contained in:
Matthias
2020-08-12 15:28:51 +02:00
55 changed files with 989 additions and 216 deletions

View File

@@ -736,7 +736,7 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
args = [
"hyperopt-list",
"--no-details"
"--no-details",
]
pargs = get_args(args)
pargs['config'] = None
@@ -749,7 +749,7 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
args = [
"hyperopt-list",
"--best",
"--no-details"
"--no-details",
]
pargs = get_args(args)
pargs['config'] = None
@@ -763,7 +763,7 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
args = [
"hyperopt-list",
"--profitable",
"--no-details"
"--no-details",
]
pargs = get_args(args)
pargs['config'] = None
@@ -776,7 +776,7 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
" 11/12", " 12/12"])
args = [
"hyperopt-list",
"--profitable"
"--profitable",
]
pargs = get_args(args)
pargs['config'] = None
@@ -792,7 +792,7 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
"hyperopt-list",
"--no-details",
"--no-color",
"--min-trades", "20"
"--min-trades", "20",
]
pargs = get_args(args)
pargs['config'] = None
@@ -806,7 +806,7 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
"hyperopt-list",
"--profitable",
"--no-details",
"--max-trades", "20"
"--max-trades", "20",
]
pargs = get_args(args)
pargs['config'] = None
@@ -821,7 +821,7 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
"hyperopt-list",
"--profitable",
"--no-details",
"--min-avg-profit", "0.11"
"--min-avg-profit", "0.11",
]
pargs = get_args(args)
pargs['config'] = None
@@ -835,7 +835,7 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
args = [
"hyperopt-list",
"--no-details",
"--max-avg-profit", "0.10"
"--max-avg-profit", "0.10",
]
pargs = get_args(args)
pargs['config'] = None
@@ -849,7 +849,7 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
args = [
"hyperopt-list",
"--no-details",
"--min-total-profit", "0.4"
"--min-total-profit", "0.4",
]
pargs = get_args(args)
pargs['config'] = None
@@ -863,7 +863,35 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
args = [
"hyperopt-list",
"--no-details",
"--max-total-profit", "0.4"
"--max-total-profit", "0.4",
]
pargs = get_args(args)
pargs['config'] = None
start_hyperopt_list(pargs)
captured = capsys.readouterr()
assert all(x in captured.out
for x in [" 1/12", " 2/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12",
" 9/12", " 11/12"])
assert all(x not in captured.out
for x in [" 4/12", " 10/12", " 12/12"])
args = [
"hyperopt-list",
"--no-details",
"--min-objective", "0.1",
]
pargs = get_args(args)
pargs['config'] = None
start_hyperopt_list(pargs)
captured = capsys.readouterr()
assert all(x in captured.out
for x in [" 10/12"])
assert all(x not in captured.out
for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12",
" 9/12", " 11/12", " 12/12"])
args = [
"hyperopt-list",
"--no-details",
"--max-objective", "0.1",
]
pargs = get_args(args)
pargs['config'] = None
@@ -878,7 +906,7 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
"hyperopt-list",
"--profitable",
"--no-details",
"--min-avg-time", "2000"
"--min-avg-time", "2000",
]
pargs = get_args(args)
pargs['config'] = None
@@ -892,7 +920,7 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
args = [
"hyperopt-list",
"--no-details",
"--max-avg-time", "1500"
"--max-avg-time", "1500",
]
pargs = get_args(args)
pargs['config'] = None
@@ -906,7 +934,7 @@ def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
args = [
"hyperopt-list",
"--no-details",
"--export-csv", "test_file.csv"
"--export-csv", "test_file.csv",
]
pargs = get_args(args)
pargs['config'] = None
@@ -1089,7 +1117,7 @@ def test_show_trades(mocker, fee, capsys, caplog):
pargs = get_args(args)
pargs['config'] = None
start_show_trades(pargs)
assert log_has("Printing 3 Trades: ", caplog)
assert log_has("Printing 4 Trades: ", caplog)
captured = capsys.readouterr()
assert "Trade(id=1" in captured.out
assert "Trade(id=2" in captured.out

View File

@@ -201,6 +201,20 @@ def create_mock_trades(fee):
)
Trade.session.add(trade)
trade = Trade(
pair='XRP/BTC',
stake_amount=0.001,
amount=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.05,
close_rate=0.06,
close_profit=0.01,
exchange='bittrex',
is_open=False,
)
Trade.session.add(trade)
# Simulate prod entry
trade = Trade(
pair='ETC/BTC',
@@ -664,7 +678,8 @@ def shitcoinmarkets(markets):
Fixture with shitcoin markets - used to test filters in pairlists
"""
shitmarkets = deepcopy(markets)
shitmarkets.update({'HOT/BTC': {
shitmarkets.update({
'HOT/BTC': {
'id': 'HOTBTC',
'symbol': 'HOT/BTC',
'base': 'HOT',
@@ -769,7 +784,32 @@ def shitcoinmarkets(markets):
"spot": True,
"future": False,
"active": True
},
},
'ADADOUBLE/USDT': {
"percentage": True,
"tierBased": False,
"taker": 0.001,
"maker": 0.001,
"precision": {
"base": 8,
"quote": 8,
"amount": 2,
"price": 4
},
"limits": {
},
"id": "ADADOUBLEUSDT",
"symbol": "ADADOUBLE/USDT",
"base": "ADADOUBLE",
"quote": "USDT",
"baseId": "ADADOUBLE",
"quoteId": "USDT",
"info": {},
"type": "spot",
"spot": True,
"future": False,
"active": True
},
})
return shitmarkets
@@ -1391,6 +1431,28 @@ def tickers():
"quoteVolume": 0.0,
"info": {}
},
"ADADOUBLE/USDT": {
"symbol": "ADADOUBLE/USDT",
"timestamp": 1580469388244,
"datetime": "2020-01-31T11:16:28.244Z",
"high": None,
"low": None,
"bid": 0.7305,
"bidVolume": None,
"ask": 0.7342,
"askVolume": None,
"vwap": None,
"open": None,
"close": None,
"last": 0,
"previousClose": None,
"change": None,
"percentage": 2.628,
"average": None,
"baseVolume": 0.0,
"quoteVolume": 0.0,
"info": {}
},
})

View File

@@ -43,7 +43,7 @@ def test_load_trades_from_db(default_conf, fee, mocker):
trades = load_trades_from_db(db_url=default_conf['db_url'])
assert init_mock.call_count == 1
assert len(trades) == 3
assert len(trades) == 4
assert isinstance(trades, DataFrame)
assert "pair" in trades.columns
assert "open_time" in trades.columns

View File

@@ -409,3 +409,98 @@ def test_process_expectancy(mocker, edge_conf, fee, risk_reward_ratio, expectanc
final = edge._process_expectancy(trades_df)
assert len(final) == 0
assert isinstance(final, dict)
def test_process_expectancy_remove_pumps(mocker, edge_conf, fee,):
edge_conf['edge']['min_trade_number'] = 2
edge_conf['edge']['remove_pumps'] = True
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
freqtrade.exchange.get_fee = fee
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
trades = [
{'pair': 'TEST/BTC',
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:05:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:10:00.000000000'),
'open_index': 1,
'close_index': 1,
'trade_duration': '',
'open_rate': 17,
'close_rate': 15,
'exit_type': 'sell_signal'},
{'pair': 'TEST/BTC',
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:20:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:25:00.000000000'),
'open_index': 4,
'close_index': 4,
'trade_duration': '',
'open_rate': 20,
'close_rate': 10,
'exit_type': 'sell_signal'},
{'pair': 'TEST/BTC',
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:20:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:25:00.000000000'),
'open_index': 4,
'close_index': 4,
'trade_duration': '',
'open_rate': 20,
'close_rate': 10,
'exit_type': 'sell_signal'},
{'pair': 'TEST/BTC',
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:20:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:25:00.000000000'),
'open_index': 4,
'close_index': 4,
'trade_duration': '',
'open_rate': 20,
'close_rate': 10,
'exit_type': 'sell_signal'},
{'pair': 'TEST/BTC',
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:20:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:25:00.000000000'),
'open_index': 4,
'close_index': 4,
'trade_duration': '',
'open_rate': 20,
'close_rate': 10,
'exit_type': 'sell_signal'},
{'pair': 'TEST/BTC',
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:30:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:40:00.000000000'),
'open_index': 6,
'close_index': 7,
'trade_duration': '',
'open_rate': 26,
'close_rate': 134,
'exit_type': 'sell_signal'}
]
trades_df = DataFrame(trades)
trades_df = edge._fill_calculable_fields(trades_df)
final = edge._process_expectancy(trades_df)
assert 'TEST/BTC' in final
assert final['TEST/BTC'].stoploss == -0.9
assert final['TEST/BTC'].nb_trades == len(trades_df) - 1
assert round(final['TEST/BTC'].winrate, 10) == 0.0

View File

@@ -714,13 +714,13 @@ def test_validate_order_types(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex')
default_conf['order_types'] = {
'buy': 'limit',
'sell': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False
}
Exchange(default_conf)
type(api_mock).has = PropertyMock(return_value={'createMarketOrder': False})
@@ -730,9 +730,8 @@ def test_validate_order_types(default_conf, mocker):
'buy': 'limit',
'sell': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': 'false'
'stoploss_on_exchange': False
}
with pytest.raises(OperationalException,
match=r'Exchange .* does not support market orders.'):
Exchange(default_conf)
@@ -743,7 +742,6 @@ def test_validate_order_types(default_conf, mocker):
'stoploss': 'limit',
'stoploss_on_exchange': True
}
with pytest.raises(OperationalException,
match=r'On exchange stoploss is not supported for .*'):
Exchange(default_conf)
@@ -1820,7 +1818,7 @@ def test_cancel_order_with_result_error(default_conf, mocker, exchange_name, cap
res = exchange.cancel_order_with_result('1234', 'ETH/BTC', 1541)
assert isinstance(res, dict)
assert log_has("Could not cancel order 1234.", caplog)
assert log_has("Could not cancel order 1234 for ETH/BTC.", caplog)
assert log_has("Could not fetch cancelled order 1234.", caplog)
assert res['amount'] == 1541
@@ -1898,10 +1896,10 @@ def test_fetch_order(default_conf, mocker, exchange_name):
assert tm.call_args_list[1][0][0] == 2
assert tm.call_args_list[2][0][0] == 5
assert tm.call_args_list[3][0][0] == 10
assert api_mock.fetch_order.call_count == API_RETRY_COUNT + 1
assert api_mock.fetch_order.call_count == 6
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
'fetch_order', 'fetch_order',
'fetch_order', 'fetch_order', retries=6,
order_id='_', pair='TKN/BTC')
@@ -1934,6 +1932,7 @@ def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
'fetch_stoploss_order', 'fetch_order',
retries=6,
order_id='_', pair='TKN/BTC')
@@ -2317,6 +2316,18 @@ def test_calculate_fee_rate(mocker, default_conf, order, expected) -> None:
(3, 3, 1),
(0, 1, 2),
(1, 1, 1),
(0, 4, 17),
(1, 4, 10),
(2, 4, 5),
(3, 4, 2),
(4, 4, 1),
(0, 5, 26),
(1, 5, 17),
(2, 5, 10),
(3, 5, 5),
(4, 5, 2),
(5, 5, 1),
])
def test_calculate_backoff(retrycount, max_retries, expected):
assert calculate_backoff(retrycount, max_retries) == expected

View File

@@ -154,4 +154,5 @@ def test_fetch_stoploss_order(default_conf, mocker):
ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'ftx',
'fetch_stoploss_order', 'fetch_orders',
retries=6,
order_id='_', pair='TKN/BTC')

View File

@@ -308,6 +308,11 @@ def test_data_with_fee(default_conf, mocker, testdatadir) -> None:
assert backtesting.fee == 0.1234
assert fee_mock.call_count == 0
default_conf['fee'] = 0.0
backtesting = Backtesting(default_conf)
assert backtesting.fee == 0.0
assert fee_mock.call_count == 0
def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
patch_exchange(mocker)

View File

@@ -235,7 +235,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}],
"BTC", ['HOT/BTC', 'FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC']),
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}],
"USDT", ['ETH/USDT', 'NANO/USDT', 'ADAHALF/USDT']),
"USDT", ['ETH/USDT', 'NANO/USDT', 'ADAHALF/USDT', 'ADADOUBLE/USDT']),
# No pair for ETH, VolumePairList
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}],
"ETH", []),
@@ -275,11 +275,16 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
{"method": "PriceFilter", "low_price_ratio": 0.03}],
"USDT", ['ETH/USDT', 'NANO/USDT']),
# Hot is removed by precision_filter, Fuel by low_price_filter.
# Hot is removed by precision_filter, Fuel by low_price_ratio, Ripple by min_price.
([{"method": "VolumePairList", "number_assets": 6, "sort_key": "quoteVolume"},
{"method": "PrecisionFilter"},
{"method": "PriceFilter", "low_price_ratio": 0.02}],
"BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']),
{"method": "PriceFilter", "low_price_ratio": 0.02, "min_price": 0.01}],
"BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']),
# Hot is removed by precision_filter, Fuel by low_price_ratio, Ethereum by max_price.
([{"method": "VolumePairList", "number_assets": 6, "sort_key": "quoteVolume"},
{"method": "PrecisionFilter"},
{"method": "PriceFilter", "low_price_ratio": 0.02, "max_price": 0.05}],
"BTC", ['TKN/BTC', 'LTC/BTC', 'XRP/BTC']),
# HOT and XRP are removed because below 1250 quoteVolume
([{"method": "VolumePairList", "number_assets": 5,
"sort_key": "quoteVolume", "min_value": 1250}],
@@ -298,11 +303,11 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
# ShuffleFilter
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
{"method": "ShuffleFilter", "seed": 77}],
"USDT", ['ETH/USDT', 'ADAHALF/USDT', 'NANO/USDT']),
"USDT", ['ADADOUBLE/USDT', 'ETH/USDT', 'NANO/USDT', 'ADAHALF/USDT']),
# ShuffleFilter, other seed
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
{"method": "ShuffleFilter", "seed": 42}],
"USDT", ['NANO/USDT', 'ETH/USDT', 'ADAHALF/USDT']),
"USDT", ['ADAHALF/USDT', 'NANO/USDT', 'ADADOUBLE/USDT', 'ETH/USDT']),
# ShuffleFilter, no seed
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
{"method": "ShuffleFilter"}],
@@ -319,7 +324,7 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
"BTC", 'filter_at_the_beginning'), # OperationalException expected
# PriceFilter after StaticPairList
([{"method": "StaticPairList"},
{"method": "PriceFilter", "low_price_ratio": 0.02}],
{"method": "PriceFilter", "low_price_ratio": 0.02, "min_price": 0.000001, "max_price": 0.1}],
"BTC", ['ETH/BTC', 'TKN/BTC']),
# PriceFilter only
([{"method": "PriceFilter", "low_price_ratio": 0.02}],
@@ -342,6 +347,9 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"},
{"method": "StaticPairList"}],
"BTC", 'static_in_the_middle'),
([{"method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume"},
{"method": "PriceFilter", "low_price_ratio": 0.02}],
"USDT", ['ETH/USDT', 'NANO/USDT']),
])
def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers,
ohlcv_history_list, pairlists, base_currency,
@@ -396,6 +404,10 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
r'would be <= stop limit.*', caplog)
if pairlist['method'] == 'PriceFilter' and whitelist_result:
assert (log_has_re(r'^Removed .* from whitelist, because 1 unit is .*%$', caplog) or
log_has_re(r'^Removed .* from whitelist, '
r'because last price < .*%$', caplog) or
log_has_re(r'^Removed .* from whitelist, '
r'because last price > .*%$', caplog) or
log_has_re(r"^Removed .* from whitelist, because ticker\['last'\] "
r"is empty.*", caplog))
if pairlist['method'] == 'VolumePairList':
@@ -524,6 +536,37 @@ 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):
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10},
{'method': 'AgeFilter', 'min_days_listed': -1}]
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 requires min_days_listed must be >= 1'):
get_patched_freqtradebot(mocker, default_conf)
def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tickers, caplog):
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10},
{'method': 'AgeFilter', 'min_days_listed': 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'AgeFilter requires min_days_listed must not exceed '
r'exchange max request size \([0-9]+\)'):
get_patched_freqtradebot(mocker, default_conf)
def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_history_list):
mocker.patch.multiple('freqtrade.exchange.Exchange',
@@ -547,6 +590,36 @@ def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_his
assert freqtrade.exchange.get_historic_ohlcv.call_count == previous_call_count
@pytest.mark.parametrize("pairlistconfig,expected", [
({"method": "PriceFilter", "low_price_ratio": 0.001, "min_price": 0.00000010,
"max_price": 1.0}, "[{'PriceFilter': 'PriceFilter - Filtering pairs priced below "
"0.1% or below 0.00000010 or above 1.00000000.'}]"
),
({"method": "PriceFilter", "low_price_ratio": 0.001, "min_price": 0.00000010},
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.1% or below 0.00000010.'}]"
),
({"method": "PriceFilter", "low_price_ratio": 0.001, "max_price": 1.00010000},
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.1% or above 1.00010000.'}]"
),
({"method": "PriceFilter", "min_price": 0.00002000},
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.00002000.'}]"
),
({"method": "PriceFilter"},
"[{'PriceFilter': 'PriceFilter - No price filters configured.'}]"
),
])
def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig, expected):
mocker.patch.multiple('freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets),
exchange_has=MagicMock(return_value=True)
)
whitelist_conf['pairlists'] = [pairlistconfig]
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
short_desc = str(freqtrade.pairlists.short_desc())
assert short_desc == expected
def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog):
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))

View File

@@ -8,7 +8,7 @@ import pytest
from numpy import isnan
from freqtrade.edge import PairInfo
from freqtrade.exceptions import ExchangeError, TemporaryError
from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
from freqtrade.persistence import Trade
from freqtrade.rpc import RPC, RPCException
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
@@ -286,12 +286,66 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee):
assert isinstance(trades['trades'][1], dict)
trades = rpc._rpc_trade_history(0)
assert len(trades['trades']) == 3
assert trades['trades_count'] == 3
# The first trade is for ETH ... sorting is descending
assert trades['trades'][-1]['pair'] == 'ETH/BTC'
assert trades['trades'][0]['pair'] == 'ETC/BTC'
assert trades['trades'][1]['pair'] == 'ETC/BTC'
assert len(trades['trades']) == 2
assert trades['trades_count'] == 2
# The first closed trade is for ETC ... sorting is descending
assert trades['trades'][-1]['pair'] == 'ETC/BTC'
assert trades['trades'][0]['pair'] == 'XRP/BTC'
def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog):
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
stoploss_mock = MagicMock()
cancel_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets),
cancel_order=cancel_mock,
cancel_stoploss_order=stoploss_mock,
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
freqtradebot.strategy.order_types['stoploss_on_exchange'] = True
create_mock_trades(fee)
rpc = RPC(freqtradebot)
with pytest.raises(RPCException, match='invalid argument'):
rpc._rpc_delete('200')
create_mock_trades(fee)
trades = Trade.query.all()
trades[1].stoploss_order_id = '1234'
trades[2].stoploss_order_id = '1234'
assert len(trades) > 2
res = rpc._rpc_delete('1')
assert isinstance(res, dict)
assert res['result'] == 'success'
assert res['trade_id'] == '1'
assert res['cancel_order_count'] == 1
assert cancel_mock.call_count == 1
assert stoploss_mock.call_count == 0
cancel_mock.reset_mock()
stoploss_mock.reset_mock()
res = rpc._rpc_delete('2')
assert isinstance(res, dict)
assert cancel_mock.call_count == 1
assert stoploss_mock.call_count == 1
assert res['cancel_order_count'] == 2
stoploss_mock = mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
side_effect=InvalidOrderException)
res = rpc._rpc_delete('3')
assert stoploss_mock.call_count == 1
stoploss_mock.reset_mock()
cancel_mock = mocker.patch('freqtrade.exchange.Exchange.cancel_order',
side_effect=InvalidOrderException)
res = rpc._rpc_delete('4')
assert cancel_mock.call_count == 1
assert stoploss_mock.call_count == 0
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,

View File

@@ -50,6 +50,12 @@ def client_get(client, url):
'Origin': 'http://example.com'})
def client_delete(client, url):
# Add fake Origin to ensure CORS kicks in
return client.delete(url, headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS),
'Origin': 'http://example.com'})
def assert_response(response, expected_code=200, needs_cors=True):
assert response.status_code == expected_code
assert response.content_type == "application/json"
@@ -352,7 +358,7 @@ def test_api_daily(botclient, mocker, ticker, fee, markets):
assert rc.json['data'][0]['date'] == str(datetime.utcnow().date())
def test_api_trades(botclient, mocker, ticker, fee, markets):
def test_api_trades(botclient, mocker, fee, markets):
ftbot, client = botclient
patch_get_signal(ftbot, (True, False))
mocker.patch.multiple(
@@ -368,12 +374,53 @@ def test_api_trades(botclient, mocker, ticker, fee, markets):
rc = client_get(client, f"{BASE_URI}/trades")
assert_response(rc)
assert len(rc.json['trades']) == 3
assert rc.json['trades_count'] == 3
rc = client_get(client, f"{BASE_URI}/trades?limit=2")
assert_response(rc)
assert len(rc.json['trades']) == 2
assert rc.json['trades_count'] == 2
rc = client_get(client, f"{BASE_URI}/trades?limit=1")
assert_response(rc)
assert len(rc.json['trades']) == 1
assert rc.json['trades_count'] == 1
def test_api_delete_trade(botclient, mocker, fee, markets):
ftbot, client = botclient
patch_get_signal(ftbot, (True, False))
stoploss_mock = MagicMock()
cancel_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets),
cancel_order=cancel_mock,
cancel_stoploss_order=stoploss_mock,
)
rc = client_delete(client, f"{BASE_URI}/trades/1")
# Error - trade won't exist yet.
assert_response(rc, 502)
create_mock_trades(fee)
ftbot.strategy.order_types['stoploss_on_exchange'] = True
trades = Trade.query.all()
trades[1].stoploss_order_id = '1234'
assert len(trades) > 2
rc = client_delete(client, f"{BASE_URI}/trades/1")
assert_response(rc)
assert rc.json['result_msg'] == 'Deleted trade 1. Closed 1 open orders.'
assert len(trades) - 1 == len(Trade.query.all())
assert cancel_mock.call_count == 1
cancel_mock.reset_mock()
rc = client_delete(client, f"{BASE_URI}/trades/1")
# Trade is gone now.
assert_response(rc, 502)
assert cancel_mock.call_count == 0
assert len(trades) - 1 == len(Trade.query.all())
rc = client_delete(client, f"{BASE_URI}/trades/2")
assert_response(rc)
assert rc.json['result_msg'] == 'Deleted trade 2. Closed 2 open orders.'
assert len(trades) - 2 == len(Trade.query.all())
assert stoploss_mock.call_count == 1
def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):

View File

@@ -21,8 +21,9 @@ from freqtrade.rpc import RPCMessageType
from freqtrade.rpc.telegram import Telegram, authorized_only
from freqtrade.state import State
from freqtrade.strategy.interface import SellType
from tests.conftest import (get_patched_freqtradebot, log_has, patch_exchange,
patch_get_signal, patch_whitelist)
from tests.conftest import (create_mock_trades, get_patched_freqtradebot,
log_has, patch_exchange, patch_get_signal,
patch_whitelist)
class DummyCls(Telegram):
@@ -60,7 +61,7 @@ def test__init__(default_conf, mocker) -> None:
assert telegram._config == default_conf
def test_init(default_conf, mocker, caplog) -> None:
def test_telegram_init(default_conf, mocker, caplog) -> None:
start_polling = MagicMock()
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock(return_value=start_polling))
@@ -72,10 +73,10 @@ def test_init(default_conf, mocker, caplog) -> None:
assert start_polling.start_polling.call_count == 1
message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], "
"['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], "
"['performance'], ['daily'], ['count'], ['reload_config', 'reload_conf'], "
"['show_config', 'show_conf'], ['stopbuy'], ['whitelist'], ['blacklist'], "
"['edge'], ['help'], ['version']]")
"['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], "
"['delete'], ['performance'], ['daily'], ['count'], ['reload_config', "
"'reload_conf'], ['show_config', 'show_conf'], ['stopbuy'], "
"['whitelist'], ['blacklist'], ['edge'], ['help'], ['version']]")
assert log_has(message_str, caplog)
@@ -725,6 +726,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
last_msg = rpc_mock.call_args_list[-1][0][0]
assert {
'type': RPCMessageType.SELL_NOTIFICATION,
'trade_id': 1,
'exchange': 'Bittrex',
'pair': 'ETH/BTC',
'gain': 'profit',
@@ -784,6 +786,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
last_msg = rpc_mock.call_args_list[-1][0][0]
assert {
'type': RPCMessageType.SELL_NOTIFICATION,
'trade_id': 1,
'exchange': 'Bittrex',
'pair': 'ETH/BTC',
'gain': 'loss',
@@ -832,6 +835,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
msg = rpc_mock.call_args_list[0][0][0]
assert {
'type': RPCMessageType.SELL_NOTIFICATION,
'trade_id': 1,
'exchange': 'Bittrex',
'pair': 'ETH/BTC',
'gain': 'loss',
@@ -1143,6 +1147,63 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
assert 'Pair Winrate Expectancy Stoploss' in msg_mock.call_args_list[0][0][0]
def test_telegram_trades(mocker, update, default_conf, fee):
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
_send_msg=msg_mock
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot)
context = MagicMock()
context.args = []
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()
context.args = [5]
telegram._trades(update=update, context=context)
msg_mock.call_count == 1
assert "2 recent trades</b>:" in msg_mock.call_args_list[0][0][0]
assert "Profit (" in msg_mock.call_args_list[0][0][0]
assert "Open Date" in msg_mock.call_args_list[0][0][0]
assert "<pre>" in msg_mock.call_args_list[0][0][0]
def test_telegram_delete_trade(mocker, update, default_conf, fee):
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
_send_msg=msg_mock
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot)
context = MagicMock()
context.args = []
telegram._delete_trade(update=update, context=context)
assert "invalid argument" in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
create_mock_trades(fee)
context = MagicMock()
context.args = [1]
telegram._delete_trade(update=update, context=context)
msg_mock.call_count == 1
assert "Deleted trade 1." in msg_mock.call_args_list[0][0][0]
assert "Please make sure to take care of this asset" in msg_mock.call_args_list[0][0][0]
def test_help_handle(default_conf, update, mocker) -> None:
msg_mock = MagicMock()
mocker.patch.multiple(

View File

@@ -871,6 +871,14 @@ def test_load_config_default_exchange_name(all_conf) -> None:
validate_config_schema(all_conf)
def test_load_config_stoploss_exchange_limit_ratio(all_conf) -> None:
all_conf['order_types']['stoploss_on_exchange_limit_ratio'] = 1.15
with pytest.raises(ValidationError,
match=r"1.15 is greater than the maximum"):
validate_config_schema(all_conf)
@pytest.mark.parametrize("keys", [("exchange", "sandbox", False),
("exchange", "key", ""),
("exchange", "secret", ""),

View File

@@ -1660,6 +1660,7 @@ def test_exit_positions_exception(mocker, default_conf, limit_buy_order, caplog)
trade = MagicMock()
trade.open_order_id = None
trade.open_fee = 0.001
trade.pair = 'ETH/BTC'
trades = [trade]
# Test raise of DependencyException exception
@@ -1669,7 +1670,7 @@ def test_exit_positions_exception(mocker, default_conf, limit_buy_order, caplog)
)
n = freqtrade.exit_positions(trades)
assert n == 0
assert log_has('Unable to sell trade: ', caplog)
assert log_has('Unable to sell trade ETH/BTC: ', caplog)
def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> None:
@@ -1726,6 +1727,7 @@ def test_update_trade_state_withorderdict(default_conf, trades_for_order, limit_
amount=amount,
exchange='binance',
open_rate=0.245441,
open_date=arrow.utcnow().datetime,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_order_id="123456",
@@ -1816,6 +1818,7 @@ def test_update_trade_state_sell(default_conf, trades_for_order, limit_sell_orde
open_rate=0.245441,
fee_open=0.0025,
fee_close=0.0025,
open_date=arrow.utcnow().datetime,
open_order_id="123456",
is_open=True,
)
@@ -2023,11 +2026,16 @@ def test_check_handle_timedout_buy_usercustom(default_conf, ticker, limit_buy_or
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock(return_value=limit_buy_order_old)
cancel_buy_order = deepcopy(limit_buy_order_old)
cancel_buy_order['status'] = 'canceled'
cancel_order_wr_mock = MagicMock(return_value=cancel_buy_order)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
fetch_order=MagicMock(return_value=limit_buy_order_old),
cancel_order_with_result=cancel_order_wr_mock,
cancel_order=cancel_order_mock,
get_fee=fee
)
@@ -2060,7 +2068,7 @@ def test_check_handle_timedout_buy_usercustom(default_conf, ticker, limit_buy_or
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=True)
# Trade should be closed since the function returns true
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 1
assert cancel_order_wr_mock.call_count == 1
assert rpc_mock.call_count == 1
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
nb_trades = len(trades)
@@ -2071,7 +2079,9 @@ def test_check_handle_timedout_buy_usercustom(default_conf, ticker, limit_buy_or
def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, open_trade,
fee, mocker) -> None:
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock(return_value=limit_buy_order_old)
limit_buy_cancel = deepcopy(limit_buy_order_old)
limit_buy_cancel['status'] = 'canceled'
cancel_order_mock = MagicMock(return_value=limit_buy_cancel)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -2259,7 +2269,10 @@ def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old,
def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old_partial,
open_trade, mocker) -> None:
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial)
limit_buy_canceled = deepcopy(limit_buy_order_old_partial)
limit_buy_canceled['status'] = 'canceled'
cancel_order_mock = MagicMock(return_value=limit_buy_canceled)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -2392,7 +2405,11 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
cancel_order_mock = MagicMock(return_value=limit_buy_order)
cancel_buy_order = deepcopy(limit_buy_order)
cancel_buy_order['status'] = 'canceled'
del cancel_buy_order['filled']
cancel_order_mock = MagicMock(return_value=cancel_buy_order)
mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
freqtrade = FreqtradeBot(default_conf)
@@ -2412,9 +2429,12 @@ def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> Non
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
assert cancel_order_mock.call_count == 1
limit_buy_order['filled'] = 2
mocker.patch('freqtrade.exchange.Exchange.cancel_order', side_effect=InvalidOrderException)
# Order remained open for some reason (cancel failed)
cancel_buy_order['status'] = 'open'
cancel_order_mock = MagicMock(return_value=cancel_buy_order)
mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
assert log_has_re(r"Order .* for .* not cancelled.", caplog)
@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'],
@@ -2572,6 +2592,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
assert rpc_mock.call_count == 1
last_msg = rpc_mock.call_args_list[-1][0][0]
assert {
'trade_id': 1,
'type': RPCMessageType.SELL_NOTIFICATION,
'exchange': 'Bittrex',
'pair': 'ETH/BTC',
@@ -2622,6 +2643,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
last_msg = rpc_mock.call_args_list[-1][0][0]
assert {
'type': RPCMessageType.SELL_NOTIFICATION,
'trade_id': 1,
'exchange': 'Bittrex',
'pair': 'ETH/BTC',
'gain': 'loss',
@@ -2678,6 +2700,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
assert {
'type': RPCMessageType.SELL_NOTIFICATION,
'trade_id': 1,
'exchange': 'Bittrex',
'pair': 'ETH/BTC',
'gain': 'loss',
@@ -2883,6 +2906,7 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
last_msg = rpc_mock.call_args_list[-1][0][0]
assert {
'type': RPCMessageType.SELL_NOTIFICATION,
'trade_id': 1,
'exchange': 'Bittrex',
'pair': 'ETH/BTC',
'gain': 'profit',
@@ -4090,7 +4114,7 @@ def test_cancel_all_open_orders(mocker, default_conf, fee, limit_buy_order, limi
freqtrade = get_patched_freqtradebot(mocker, default_conf)
create_mock_trades(fee)
trades = Trade.query.all()
assert len(trades) == 3
assert len(trades) == 4
freqtrade.cancel_all_open_orders()
assert buy_mock.call_count == 1
assert sell_mock.call_count == 1

View File

@@ -995,7 +995,7 @@ def test_get_overall_performance(fee):
create_mock_trades(fee)
res = Trade.get_overall_performance()
assert len(res) == 1
assert len(res) == 2
assert 'pair' in res[0]
assert 'profit' in res[0]
assert 'count' in res[0]
@@ -1010,5 +1010,5 @@ def test_get_best_pair(fee):
create_mock_trades(fee)
res = Trade.get_best_pair()
assert len(res) == 2
assert res[0] == 'ETC/BTC'
assert res[1] == 0.005
assert res[0] == 'XRP/BTC'
assert res[1] == 0.01

View File

@@ -21,7 +21,7 @@ from freqtrade.plot.plotting import (add_indicators, add_profit,
load_and_plot_trades, plot_profit,
plot_trades, store_plot_file)
from freqtrade.resolvers import StrategyResolver
from tests.conftest import get_args, log_has, log_has_re
from tests.conftest import get_args, log_has, log_has_re, patch_exchange
def fig_generating_mock(fig, *args, **kwargs):
@@ -316,6 +316,8 @@ def test_start_plot_dataframe(mocker):
def test_load_and_plot_trades(default_conf, mocker, caplog, testdatadir):
patch_exchange(mocker)
default_conf['trade_source'] = 'file'
default_conf["datadir"] = testdatadir
default_conf['exportfilename'] = testdatadir / "backtest-result_test.json"