merged with develop

This commit is contained in:
Sam Germain
2021-09-19 15:35:54 -06:00
27 changed files with 798 additions and 444 deletions

View File

@@ -12,7 +12,8 @@ from freqtrade.persistence import Trade
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.plugins.pairlistmanager import PairListManager
from freqtrade.resolvers import PairListResolver
from tests.conftest import get_patched_exchange, get_patched_freqtradebot, log_has, log_has_re
from tests.conftest import (create_mock_trades, get_patched_exchange, get_patched_freqtradebot,
log_has, log_has_re)
@pytest.fixture(scope="function")
@@ -663,6 +664,31 @@ def test_PerformanceFilter_error(mocker, whitelist_conf, caplog) -> None:
assert log_has("PerformanceFilter is not available in this mode.", caplog)
@pytest.mark.usefixtures("init_persistence")
def test_PerformanceFilter_lookback(mocker, whitelist_conf, fee) -> None:
whitelist_conf['exchange']['pair_whitelist'].append('XRP/BTC')
whitelist_conf['pairlists'] = [
{"method": "StaticPairList"},
{"method": "PerformanceFilter", "minutes": 60}
]
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
exchange = get_patched_exchange(mocker, whitelist_conf)
pm = PairListManager(exchange, whitelist_conf)
pm.refresh_pairlist()
assert pm.whitelist == ['ETH/BTC', 'TKN/BTC', 'XRP/BTC']
with time_machine.travel("2021-09-01 05:00:00 +00:00") as t:
create_mock_trades(fee)
pm.refresh_pairlist()
assert pm.whitelist == ['XRP/BTC', 'ETH/BTC', 'TKN/BTC']
# Move to "outside" of lookback window, so original sorting is restored.
t.move_to("2021-09-01 07:00:00 +00:00")
pm.refresh_pairlist()
assert pm.whitelist == ['ETH/BTC', 'TKN/BTC', 'XRP/BTC']
def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None:
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}]

View File

@@ -422,20 +422,22 @@ def test_api_stopbuy(botclient):
assert ftbot.config['max_open_trades'] == 0
def test_api_balance(botclient, mocker, rpc_balance):
def test_api_balance(botclient, mocker, rpc_balance, tickers):
ftbot, client = botclient
ftbot.config['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance)
mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers)
mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination',
side_effect=lambda a, b: f"{a}/{b}")
ftbot.wallets.update()
rc = client_get(client, f"{BASE_URI}/balance")
assert_response(rc)
assert "currencies" in rc.json()
assert len(rc.json()["currencies"]) == 5
assert rc.json()['currencies'][0] == {
response = rc.json()
assert "currencies" in response
assert len(response["currencies"]) == 5
assert response['currencies'][0] == {
'currency': 'BTC',
'free': 12.0,
'balance': 12.0,
@@ -443,6 +445,10 @@ def test_api_balance(botclient, mocker, rpc_balance):
'est_stake': 12.0,
'stake': 'BTC',
}
assert 'starting_capital' in response
assert 'starting_capital_fiat' in response
assert 'starting_capital_pct' in response
assert 'starting_capital_ratio' in response
def test_api_count(botclient, mocker, ticker, fee, markets):
@@ -1218,6 +1224,7 @@ def test_api_strategies(botclient):
assert_response(rc)
assert rc.json() == {'strategies': [
'HyperoptableStrategy',
'InformativeDecoratorTest',
'StrategyTestV2',
'TestStrategyLegacyV1'
]}

View File

@@ -576,6 +576,8 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None
'total': 100.0,
'symbol': 100.0,
'value': 1000.0,
'starting_capital': 1000,
'starting_capital_fiat': 1000,
})
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)

View File

@@ -0,0 +1,75 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from pandas import DataFrame
from freqtrade.strategy import informative, merge_informative_pair
from freqtrade.strategy.interface import IStrategy
class InformativeDecoratorTest(IStrategy):
"""
Strategy used by tests freqtrade bot.
Please do not modify this strategy, it's intended for internal use only.
Please look at the SampleStrategy in the user_data/strategy directory
or strategy repository https://github.com/freqtrade/freqtrade-strategies
for samples and inspiration.
"""
INTERFACE_VERSION = 2
stoploss = -0.10
timeframe = '5m'
startup_candle_count: int = 20
def informative_pairs(self):
return [('BTC/USDT', '5m')]
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['buy'] = 0
return dataframe
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['sell'] = 0
return dataframe
# Decorator stacking test.
@informative('30m')
@informative('1h')
def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi'] = 14
return dataframe
# Simple informative test.
@informative('1h', 'BTC/{stake}')
def populate_indicators_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi'] = 14
return dataframe
# Quote currency different from stake currency test.
@informative('1h', 'ETH/BTC')
def populate_indicators_eth_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi'] = 14
return dataframe
# Formatting test.
@informative('30m', 'BTC/{stake}', '{column}_{BASE}_{QUOTE}_{base}_{quote}_{asset}_{timeframe}')
def populate_indicators_btc_1h_2(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi'] = 14
return dataframe
# Custom formatter test
@informative('30m', 'ETH/{stake}', fmt=lambda column, **kwargs: column + '_from_callable')
def populate_indicators_eth_30m(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi'] = 14
return dataframe
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Strategy timeframe indicators for current pair.
dataframe['rsi'] = 14
# Informative pairs are available in this method.
dataframe['rsi_less'] = dataframe['rsi'] < dataframe['rsi_1h']
# Mixing manual informative pairs with decorators.
informative = self.dp.get_pair_dataframe('BTC/USDT', '5m')
informative['rsi'] = 14
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, '5m', ffill=True)
return dataframe

View File

@@ -611,7 +611,7 @@ def test_is_informative_pairs_callback(default_conf):
strategy = StrategyResolver.load_strategy(default_conf)
# Should return empty
# Uses fallback to base implementation
assert [] == strategy.informative_pairs()
assert [] == strategy.gather_informative_pairs()
@pytest.mark.parametrize('error', [

View File

@@ -4,7 +4,9 @@ import numpy as np
import pandas as pd
import pytest
from freqtrade.strategy import merge_informative_pair, stoploss_from_open, timeframe_to_minutes
from freqtrade.data.dataprovider import DataProvider
from freqtrade.strategy import (merge_informative_pair, stoploss_from_absolute, stoploss_from_open,
timeframe_to_minutes)
def generate_test_data(timeframe: str, size: int):
@@ -132,3 +134,65 @@ def test_stoploss_from_open():
assert stoploss == 0
else:
assert isclose(stop_price, expected_stop_price, rel_tol=0.00001)
def test_stoploss_from_absolute():
assert stoploss_from_absolute(90, 100) == 1 - (90 / 100)
assert stoploss_from_absolute(100, 100) == 0
assert stoploss_from_absolute(110, 100) == 0
assert stoploss_from_absolute(100, 0) == 1
assert stoploss_from_absolute(0, 100) == 1
def test_informative_decorator(mocker, default_conf):
test_data_5m = generate_test_data('5m', 40)
test_data_30m = generate_test_data('30m', 40)
test_data_1h = generate_test_data('1h', 40)
data = {
('XRP/USDT', '5m'): test_data_5m,
('XRP/USDT', '30m'): test_data_30m,
('XRP/USDT', '1h'): test_data_1h,
('LTC/USDT', '5m'): test_data_5m,
('LTC/USDT', '30m'): test_data_30m,
('LTC/USDT', '1h'): test_data_1h,
('BTC/USDT', '30m'): test_data_30m,
('BTC/USDT', '5m'): test_data_5m,
('BTC/USDT', '1h'): test_data_1h,
('ETH/USDT', '1h'): test_data_1h,
('ETH/USDT', '30m'): test_data_30m,
('ETH/BTC', '1h'): test_data_1h,
}
from .strats.informative_decorator_strategy import InformativeDecoratorTest
default_conf['stake_currency'] = 'USDT'
strategy = InformativeDecoratorTest(config=default_conf)
strategy.dp = DataProvider({}, None, None)
mocker.patch.object(strategy.dp, 'current_whitelist', return_value=[
'XRP/USDT', 'LTC/USDT', 'BTC/USDT'
])
assert len(strategy._ft_informative) == 6 # Equal to number of decorators used
informative_pairs = [('XRP/USDT', '1h'), ('LTC/USDT', '1h'), ('XRP/USDT', '30m'),
('LTC/USDT', '30m'), ('BTC/USDT', '1h'), ('BTC/USDT', '30m'),
('BTC/USDT', '5m'), ('ETH/BTC', '1h'), ('ETH/USDT', '30m')]
for inf_pair in informative_pairs:
assert inf_pair in strategy.gather_informative_pairs()
def test_historic_ohlcv(pair, timeframe):
return data[(pair, timeframe or strategy.timeframe)].copy()
mocker.patch('freqtrade.data.dataprovider.DataProvider.historic_ohlcv',
side_effect=test_historic_ohlcv)
analyzed = strategy.advise_all_indicators(
{p: data[(p, strategy.timeframe)] for p in ('XRP/USDT', 'LTC/USDT')})
expected_columns = [
'rsi_1h', 'rsi_30m', # Stacked informative decorators
'btc_usdt_rsi_1h', # BTC 1h informative
'rsi_BTC_USDT_btc_usdt_BTC/USDT_30m', # Column formatting
'rsi_from_callable', # Custom column formatter
'eth_btc_rsi_1h', # Quote currency not matching stake currency
'rsi', 'rsi_less', # Non-informative columns
'rsi_5m', # Manual informative dataframe
]
for _, dataframe in analyzed.items():
for col in expected_columns:
assert col in dataframe.columns

View File

@@ -35,7 +35,7 @@ def test_search_all_strategies_no_failed():
directory = Path(__file__).parent / "strats"
strategies = StrategyResolver.search_all_objects(directory, enum_failed=False)
assert isinstance(strategies, list)
assert len(strategies) == 3
assert len(strategies) == 4
assert isinstance(strategies[0], dict)
@@ -43,10 +43,10 @@ def test_search_all_strategies_with_failed():
directory = Path(__file__).parent / "strats"
strategies = StrategyResolver.search_all_objects(directory, enum_failed=True)
assert isinstance(strategies, list)
assert len(strategies) == 4
assert len(strategies) == 5
# with enum_failed=True search_all_objects() shall find 2 good strategies
# and 1 which fails to load
assert len([x for x in strategies if x['class'] is not None]) == 3
assert len([x for x in strategies if x['class'] is not None]) == 4
assert len([x for x in strategies if x['class'] is None]) == 1

View File

@@ -78,11 +78,15 @@ def test_bot_cleanup(mocker, default_conf, caplog) -> None:
assert coo_mock.call_count == 1
def test_order_dict_dry_run(default_conf, mocker, caplog) -> None:
@pytest.mark.parametrize('runmode', [
RunMode.DRY_RUN,
RunMode.LIVE
])
def test_order_dict(default_conf, mocker, runmode, caplog) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
conf = default_conf.copy()
conf['runmode'] = RunMode.DRY_RUN
conf['runmode'] = runmode
conf['order_types'] = {
'buy': 'market',
'sell': 'limit',
@@ -92,45 +96,14 @@ def test_order_dict_dry_run(default_conf, mocker, caplog) -> None:
conf['bid_strategy']['price_side'] = 'ask'
freqtrade = FreqtradeBot(conf)
if runmode == RunMode.LIVE:
assert not log_has_re(".*stoploss_on_exchange .* dry-run", caplog)
assert freqtrade.strategy.order_types['stoploss_on_exchange']
caplog.clear()
# is left untouched
conf = default_conf.copy()
conf['runmode'] = RunMode.DRY_RUN
conf['order_types'] = {
'buy': 'market',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False,
}
freqtrade = FreqtradeBot(conf)
assert not freqtrade.strategy.order_types['stoploss_on_exchange']
assert not log_has_re(".*stoploss_on_exchange .* dry-run", caplog)
def test_order_dict_live(default_conf, mocker, caplog) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
conf = default_conf.copy()
conf['runmode'] = RunMode.LIVE
conf['order_types'] = {
'buy': 'market',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True,
}
conf['bid_strategy']['price_side'] = 'ask'
freqtrade = FreqtradeBot(conf)
assert not log_has_re(".*stoploss_on_exchange .* dry-run", caplog)
assert freqtrade.strategy.order_types['stoploss_on_exchange']
caplog.clear()
# is left untouched
conf = default_conf.copy()
conf['runmode'] = RunMode.LIVE
conf['runmode'] = runmode
conf['order_types'] = {
'buy': 'market',
'sell': 'limit',
@@ -219,8 +192,14 @@ def test_edge_overrides_stake_amount(mocker, edge_conf) -> None:
'LTC/BTC', freqtrade.edge) == (999.9 * 0.5 * 0.01) / 0.21
def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf) -> None:
@pytest.mark.parametrize('buy_price_mult,ignore_strat_sl', [
# Override stoploss
(0.79, False),
# Override strategy stoploss
(0.85, True)
])
def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker,
buy_price_mult, ignore_strat_sl, edge_conf) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
patch_edge(mocker)
@@ -234,9 +213,9 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=MagicMock(return_value={
'bid': buy_price * 0.79,
'ask': buy_price * 0.79,
'last': buy_price * 0.79
'bid': buy_price * buy_price_mult,
'ask': buy_price * buy_price_mult,
'last': buy_price * buy_price_mult,
}),
get_fee=fee,
)
@@ -253,46 +232,10 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf
#############################################
# stoploss shoud be hit
assert freqtrade.handle_trade(trade) is True
assert log_has('Executing Sell for NEO/BTC. Reason: stop_loss', caplog)
assert trade.sell_reason == SellType.STOP_LOSS.value
def test_edge_should_ignore_strategy_stoploss(limit_buy_order, fee,
mocker, edge_conf) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
patch_edge(mocker)
edge_conf['max_open_trades'] = float('inf')
# Strategy stoploss is -0.1 but Edge imposes a stoploss at -0.2
# Thus, if price falls 15%, stoploss should not be triggered
#
# mocking the ticker: price is falling ...
buy_price = limit_buy_order['price']
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=MagicMock(return_value={
'bid': buy_price * 0.85,
'ask': buy_price * 0.85,
'last': buy_price * 0.85
}),
get_fee=fee,
)
#############################################
# Create a trade with "limit_buy_order" price
freqtrade = FreqtradeBot(edge_conf)
freqtrade.active_pair_whitelist = ['NEO/BTC']
patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
trade = Trade.query.first()
trade.update(limit_buy_order)
#############################################
# stoploss shoud not be hit
assert freqtrade.handle_trade(trade) is False
assert freqtrade.handle_trade(trade) is not ignore_strat_sl
if not ignore_strat_sl:
assert log_has('Executing Sell for NEO/BTC. Reason: stop_loss', caplog)
assert trade.sell_reason == SellType.STOP_LOSS.value
def test_total_open_trades_stakes(mocker, default_conf, ticker, fee) -> None:
@@ -376,8 +319,16 @@ def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order,
freqtrade.create_trade('ETH/BTC')
def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order_open,
fee, mocker) -> None:
@pytest.mark.parametrize('stake_amount,create,amount_enough,max_open_trades', [
(0.0005, True, True, 99),
(0.000000005, True, False, 99),
(0, False, True, 99),
(UNLIMITED_STAKE_AMOUNT, False, True, 0),
])
def test_create_trade_minimal_amount(
default_conf, ticker, limit_buy_order_open, fee, mocker,
stake_amount, create, amount_enough, max_open_trades, caplog
) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
buy_mock = MagicMock(return_value=limit_buy_order_open)
@@ -387,78 +338,33 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order_open,
create_order=buy_mock,
get_fee=fee,
)
default_conf['stake_amount'] = 0.0005
default_conf['max_open_trades'] = max_open_trades
freqtrade = FreqtradeBot(default_conf)
freqtrade.config['stake_amount'] = stake_amount
patch_get_signal(freqtrade)
freqtrade.create_trade('ETH/BTC')
rate, amount = buy_mock.call_args[1]['rate'], buy_mock.call_args[1]['amount']
assert rate * amount <= default_conf['stake_amount']
def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_order_open,
fee, mocker, caplog) -> 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,
create_order=buy_mock,
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf)
freqtrade.config['stake_amount'] = 0.000000005
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,
create_order=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')
def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order_open,
fee, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
create_order=MagicMock(return_value=limit_buy_order_open),
get_fee=fee,
)
default_conf['max_open_trades'] = 0
default_conf['stake_amount'] = UNLIMITED_STAKE_AMOUNT
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
assert not freqtrade.create_trade('ETH/BTC')
assert freqtrade.wallets.get_trade_stake_amount('ETH/BTC', freqtrade.edge) == 0
if create:
assert freqtrade.create_trade('ETH/BTC')
if amount_enough:
rate, amount = buy_mock.call_args[1]['rate'], buy_mock.call_args[1]['amount']
assert rate * amount <= default_conf['stake_amount']
else:
assert log_has_re(
r"Stake amount for pair .* is too small.*",
caplog
)
else:
assert not freqtrade.create_trade('ETH/BTC')
if not max_open_trades:
assert freqtrade.wallets.get_trade_stake_amount('ETH/BTC', freqtrade.edge) == 0
@pytest.mark.parametrize('whitelist,positions', [
(["ETH/BTC"], 1), # No pairs left
([], 0), # No pairs in whitelist
])
def test_enter_positions_no_pairs_left(default_conf, ticker, limit_buy_order_open, fee,
mocker, caplog) -> None:
whitelist, positions, mocker, caplog) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
@@ -467,36 +373,20 @@ def test_enter_positions_no_pairs_left(default_conf, ticker, limit_buy_order_ope
create_order=MagicMock(return_value=limit_buy_order_open),
get_fee=fee,
)
default_conf['exchange']['pair_whitelist'] = ["ETH/BTC"]
default_conf['exchange']['pair_whitelist'] = whitelist
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
n = freqtrade.enter_positions()
assert n == 1
assert not log_has_re(r"No currency pair in active pair whitelist.*", caplog)
n = freqtrade.enter_positions()
assert n == 0
assert log_has_re(r"No currency pair in active pair whitelist.*", caplog)
def test_enter_positions_no_pairs_in_whitelist(default_conf, ticker, limit_buy_order, fee,
mocker, caplog) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
create_order=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
)
default_conf['exchange']['pair_whitelist'] = []
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
n = freqtrade.enter_positions()
assert n == 0
assert log_has("Active pair whitelist is empty.", caplog)
assert n == positions
if positions:
assert not log_has_re(r"No currency pair in active pair whitelist.*", caplog)
n = freqtrade.enter_positions()
assert n == 0
assert log_has_re(r"No currency pair in active pair whitelist.*", caplog)
else:
assert n == 0
assert log_has("Active pair whitelist is empty.", caplog)
@pytest.mark.usefixtures("init_persistence")
@@ -1668,30 +1558,27 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
)
def test_enter_positions(mocker, default_conf, caplog) -> None:
@pytest.mark.parametrize('return_value,side_effect,log_message', [
(False, None, 'Found no buy signals for whitelisted currencies. Trying again...'),
(None, DependencyException, 'Unable to create trade for ETH/BTC: ')
])
def test_enter_positions(mocker, default_conf, return_value, side_effect,
log_message, caplog) -> None:
caplog.set_level(logging.DEBUG)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
mock_ct = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trade',
MagicMock(return_value=False))
n = freqtrade.enter_positions()
assert n == 0
assert log_has('Found no enter signals for whitelisted currencies. Trying again...', caplog)
# create_trade should be called once for every pair in the whitelist.
assert mock_ct.call_count == len(default_conf['exchange']['pair_whitelist'])
def test_enter_positions_exception(mocker, default_conf, caplog) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf)
mock_ct = mocker.patch(
'freqtrade.freqtradebot.FreqtradeBot.create_trade',
MagicMock(side_effect=DependencyException)
MagicMock(
return_value=return_value,
side_effect=side_effect
)
)
n = freqtrade.enter_positions()
assert n == 0
assert log_has(log_message, caplog)
# create_trade should be called once for every pair in the whitelist.
assert mock_ct.call_count == len(default_conf['exchange']['pair_whitelist'])
assert log_has('Unable to create trade for ETH/BTC: ', caplog)
def test_exit_positions(mocker, default_conf, limit_buy_order, caplog) -> None:
@@ -1785,8 +1672,13 @@ def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> No
assert log_has_re('Found open order for.*', caplog)
@pytest.mark.parametrize('initial_amount,has_rounding_fee', [
(90.99181073 + 1e-14, True),
(8.0, False)
])
def test_update_trade_state_withorderdict(default_conf, trades_for_order, limit_buy_order, fee,
mocker):
mocker, initial_amount, has_rounding_fee, caplog):
trades_for_order[0]['amount'] = initial_amount
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
# fetch_order should not be called!!
mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError))
@@ -1807,32 +1699,8 @@ def test_update_trade_state_withorderdict(default_conf, trades_for_order, limit_
freqtrade.update_trade_state(trade, '123456', limit_buy_order)
assert trade.amount != amount
assert trade.amount == limit_buy_order['amount']
def test_update_trade_state_withorderdict_rounding_fee(default_conf, trades_for_order, fee,
limit_buy_order, mocker, caplog):
trades_for_order[0]['amount'] = limit_buy_order['amount'] + 1e-14
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
# fetch_order should not be called!!
mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError))
patch_exchange(mocker)
amount = sum(x['amount'] for x in trades_for_order)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
trade = Trade(
pair='LTC/ETH',
amount=amount,
exchange='binance',
open_rate=0.245441,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_order_id='123456',
is_open=True,
open_date=arrow.utcnow().datetime,
)
freqtrade.update_trade_state(trade, '123456', limit_buy_order)
assert trade.amount != amount
assert trade.amount == limit_buy_order['amount']
assert log_has_re(r'Applying fee on amount for .*', caplog)
if has_rounding_fee:
assert log_has_re(r'Applying fee on amount for .*', caplog)
def test_update_trade_state_exception(mocker, default_conf,
@@ -3144,16 +3012,28 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf, ticker, fee,
assert mock_insuf.call_count == 1
def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, limit_buy_order_open,
fee, mocker) -> None:
@pytest.mark.parametrize('profit_only,bid,ask,handle_first,handle_second,sell_type', [
# Enable profit
(True, 0.00001172, 0.00001173, False, True, SellType.SELL_SIGNAL.value),
# Disable profit
(False, 0.00002172, 0.00002173, True, False, SellType.SELL_SIGNAL.value),
# Enable loss
# * Shouldn't this be SellType.STOP_LOSS.value
(True, 0.00000172, 0.00000173, False, False, None),
# Disable loss
(False, 0.00000172, 0.00000173, True, False, SellType.SELL_SIGNAL.value),
])
def test_sell_profit_only(
default_conf, limit_buy_order, limit_buy_order_open,
fee, mocker, profit_only, bid, ask, handle_first, handle_second, sell_type) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=MagicMock(return_value={
'bid': 0.00001172,
'ask': 0.00001173,
'last': 0.00001172
'bid': bid,
'ask': ask,
'last': bid
}),
create_order=MagicMock(side_effect=[
limit_buy_order_open,
@@ -3163,128 +3043,29 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, limit_buy
)
default_conf.update({
'use_sell_signal': True,
'sell_profit_only': True,
'sell_profit_only': profit_only,
'sell_profit_offset': 0.1,
})
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
if sell_type == SellType.SELL_SIGNAL.value:
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
else:
freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple(
sell_type=SellType.NONE))
freqtrade.enter_positions()
trade = Trade.query.first()
trade.update(limit_buy_order)
freqtrade.wallets.update()
patch_get_signal(freqtrade, value=(False, True, None))
assert freqtrade.handle_trade(trade) is False
assert freqtrade.handle_trade(trade) is handle_first
freqtrade.strategy.sell_profit_offset = 0.0
assert freqtrade.handle_trade(trade) is True
if handle_second:
freqtrade.strategy.sell_profit_offset = 0.0
assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == SellType.SELL_SIGNAL.value
def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, limit_buy_order_open,
fee, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=MagicMock(return_value={
'bid': 0.00002172,
'ask': 0.00002173,
'last': 0.00002172
}),
create_order=MagicMock(side_effect=[
limit_buy_order_open,
{'id': 1234553382},
]),
get_fee=fee,
)
default_conf.update({
'use_sell_signal': True,
'sell_profit_only': False,
})
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
trade = Trade.query.first()
trade.update(limit_buy_order)
freqtrade.wallets.update()
patch_get_signal(freqtrade, value=(False, True, None))
assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == SellType.SELL_SIGNAL.value
def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, limit_buy_order_open,
fee, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=MagicMock(return_value={
'bid': 0.00000172,
'ask': 0.00000173,
'last': 0.00000172
}),
create_order=MagicMock(side_effect=[
limit_buy_order_open,
{'id': 1234553382},
]),
get_fee=fee,
)
default_conf.update({
'use_sell_signal': True,
'sell_profit_only': True,
})
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple(
sell_type=SellType.NONE))
freqtrade.enter_positions()
trade = Trade.query.first()
trade.update(limit_buy_order)
patch_get_signal(freqtrade, value=(False, True, None))
assert freqtrade.handle_trade(trade) is False
def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, limit_buy_order_open,
fee, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=MagicMock(return_value={
'bid': 0.0000172,
'ask': 0.0000173,
'last': 0.0000172
}),
create_order=MagicMock(side_effect=[
limit_buy_order_open,
{'id': 1234553382},
]),
get_fee=fee,
)
default_conf.update({
'use_sell_signal': True,
'sell_profit_only': False,
})
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
trade = Trade.query.first()
trade.update(limit_buy_order)
freqtrade.wallets.update()
patch_get_signal(freqtrade, value=(False, True, None))
assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == SellType.SELL_SIGNAL.value
assert trade.sell_reason == sell_type
def test_sell_not_enough_balance(default_conf, limit_buy_order, limit_buy_order_open,
@@ -3322,11 +3103,15 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order, limit_buy_order_
assert trade.amount != amnt
def test__safe_exit_amount(default_conf, fee, caplog, mocker):
@pytest.mark.parametrize('amount_wallet,has_err', [
(95.29, False),
(91.29, True)
])
def test__safe_exit_amount(default_conf, fee, caplog, mocker, amount_wallet, has_err):
patch_RPCManager(mocker)
patch_exchange(mocker)
amount = 95.33
amount_wallet = 95.29
amount_wallet = amount_wallet
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=amount_wallet))
wallet_update = mocker.patch('freqtrade.wallets.Wallets.update')
trade = Trade(
@@ -3340,37 +3125,19 @@ def test__safe_exit_amount(default_conf, fee, caplog, mocker):
)
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
wallet_update.reset_mock()
assert freqtrade._safe_exit_amount(trade.pair, trade.amount) == amount_wallet
assert log_has_re(r'.*Falling back to wallet-amount.', caplog)
assert wallet_update.call_count == 1
caplog.clear()
wallet_update.reset_mock()
assert freqtrade._safe_exit_amount(trade.pair, amount_wallet) == amount_wallet
assert not log_has_re(r'.*Falling back to wallet-amount.', caplog)
assert wallet_update.call_count == 1
def test__safe_exit_amount_error(default_conf, fee, caplog, mocker):
patch_RPCManager(mocker)
patch_exchange(mocker)
amount = 95.33
amount_wallet = 91.29
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=amount_wallet))
trade = Trade(
pair='LTC/ETH',
amount=amount,
exchange='binance',
open_rate=0.245441,
open_order_id="123456",
fee_open=fee.return_value,
fee_close=fee.return_value,
)
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
with pytest.raises(DependencyException, match=r"Not enough amount to exit."):
assert freqtrade._safe_exit_amount(trade.pair, trade.amount)
if has_err:
with pytest.raises(DependencyException, match=r"Not enough amount to sell."):
assert freqtrade._safe_exit_amount(trade.pair, trade.amount)
else:
wallet_update.reset_mock()
assert freqtrade._safe_exit_amount(trade.pair, trade.amount) == amount_wallet
assert log_has_re(r'.*Falling back to wallet-amount.', caplog)
assert wallet_update.call_count == 1
caplog.clear()
wallet_update.reset_mock()
assert freqtrade._safe_exit_amount(trade.pair, amount_wallet) == amount_wallet
assert not log_has_re(r'.*Falling back to wallet-amount.', caplog)
assert wallet_update.call_count == 1
def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplog) -> None:
@@ -4158,50 +3925,37 @@ def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_o
assert trade is None
def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2) -> None:
@pytest.mark.parametrize('exception_thrown,ask,last,order_book_top,order_book', [
(False, 0.045, 0.046, 2, None),
(True, 0.042, 0.046, 1, {'bids': [[]], 'asks': [[]]})
])
def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2, exception_thrown,
ask, last, order_book_top, order_book, caplog) -> None:
"""
test if function get_rate will return the order book price
instead of the ask rate
test if function get_rate will return the order book price instead of the ask rate
"""
patch_exchange(mocker)
ticker_mock = MagicMock(return_value={'ask': 0.045, 'last': 0.046})
ticker_mock = MagicMock(return_value={'ask': ask, 'last': last})
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_l2_order_book=order_book_l2,
fetch_l2_order_book=MagicMock(return_value=order_book) if order_book else order_book_l2,
fetch_ticker=ticker_mock,
)
default_conf['exchange']['name'] = 'binance'
default_conf['bid_strategy']['use_order_book'] = True
default_conf['bid_strategy']['order_book_top'] = 2
default_conf['bid_strategy']['order_book_top'] = order_book_top
default_conf['bid_strategy']['ask_last_balance'] = 0
default_conf['telegram']['enabled'] = False
freqtrade = FreqtradeBot(default_conf)
assert freqtrade.exchange.get_rate('ETH/BTC', refresh=True, side="buy") == 0.043935
assert ticker_mock.call_count == 0
def test_order_book_bid_strategy_exception(mocker, default_conf, caplog) -> None:
patch_exchange(mocker)
ticker_mock = MagicMock(return_value={'ask': 0.042, 'last': 0.046})
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_l2_order_book=MagicMock(return_value={'bids': [[]], 'asks': [[]]}),
fetch_ticker=ticker_mock,
)
default_conf['exchange']['name'] = 'binance'
default_conf['bid_strategy']['use_order_book'] = True
default_conf['bid_strategy']['order_book_top'] = 1
default_conf['bid_strategy']['ask_last_balance'] = 0
default_conf['telegram']['enabled'] = False
freqtrade = FreqtradeBot(default_conf)
# orderbook shall be used even if tickers would be lower.
with pytest.raises(PricingError):
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)
if exception_thrown:
with pytest.raises(PricingError):
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)
else:
assert freqtrade.exchange.get_rate('ETH/BTC', refresh=True, side="buy") == 0.043935
assert ticker_mock.call_count == 0
def test_check_depth_of_market_buy(default_conf, mocker, order_book_l2) -> None: