Merge branch 'develop' into data_handler

This commit is contained in:
Matthias
2020-01-21 06:58:48 +01:00
50 changed files with 1140 additions and 506 deletions

View File

@@ -60,8 +60,10 @@ def patch_exchange(mocker, api_mock=None, id='bittrex', mock_markets=True) -> No
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id))
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title()))
mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2))
if mock_markets:
mocker.patch('freqtrade.exchange.Exchange.markets',
PropertyMock(return_value=get_markets()))
@@ -1317,12 +1319,12 @@ def buy_order_fee():
def edge_conf(default_conf):
conf = deepcopy(default_conf)
conf['max_open_trades'] = -1
conf['tradable_balance_ratio'] = 0.5
conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
conf['edge'] = {
"enabled": True,
"process_throttle_secs": 1800,
"calculate_since_number_of_days": 14,
"capital_available_percentage": 0.5,
"allowed_risk": 0.01,
"stoploss_range_min": -0.01,
"stoploss_range_max": -0.1,

View File

@@ -22,8 +22,8 @@ def test_stoploss_limit_order(default_conf, mocker):
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
@@ -71,8 +71,8 @@ def test_stoploss_limit_order_dry_run(default_conf, mocker):
api_mock = MagicMock()
order_type = 'stop_loss_limit'
default_conf['dry_run'] = True
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')

View File

@@ -73,6 +73,7 @@ def test_init(default_conf, mocker, caplog):
def test_init_ccxt_kwargs(default_conf, mocker, caplog):
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
caplog.set_level(logging.INFO)
conf = copy.deepcopy(default_conf)
conf['exchange']['ccxt_async_config'] = {'aiohttp_trust_env': True}
@@ -121,9 +122,10 @@ def test_init_exception(default_conf, mocker):
def test_exchange_resolver(default_conf, mocker, caplog):
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=MagicMock()))
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
exchange = ExchangeResolver.load_exchange('Bittrex', default_conf)
assert isinstance(exchange, Exchange)
assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog)
@@ -173,35 +175,78 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog):
ex.validate_order_time_in_force(tif2)
def test_symbol_amount_prec(default_conf, mocker):
@pytest.mark.parametrize("amount,precision_mode,precision,expected", [
(2.34559, 2, 4, 2.3455),
(2.34559, 2, 5, 2.34559),
(2.34559, 2, 3, 2.345),
(2.9999, 2, 3, 2.999),
(2.9909, 2, 3, 2.990),
# Tests for Tick-size
(2.34559, 4, 0.0001, 2.3455),
(2.34559, 4, 0.00001, 2.34559),
(2.34559, 4, 0.001, 2.345),
(2.9999, 4, 0.001, 2.999),
(2.9909, 4, 0.001, 2.990),
(2.9909, 4, 0.005, 2.990),
(2.9999, 4, 0.005, 2.995),
])
def test_amount_to_precision(default_conf, mocker, amount, precision_mode, precision, expected):
'''
Test rounds down to 4 Decimal places
Test rounds down
'''
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': 4}}})
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': precision}}})
exchange = get_patched_exchange(mocker, default_conf, id="binance")
# digits counting mode
# DECIMAL_PLACES = 2
# SIGNIFICANT_DIGITS = 3
# TICK_SIZE = 4
mocker.patch('freqtrade.exchange.Exchange.precisionMode',
PropertyMock(return_value=precision_mode))
mocker.patch('freqtrade.exchange.Exchange.markets', markets)
pair = 'ETH/BTC'
assert exchange.amount_to_precision(pair, amount) == expected
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
(2.34559, 2, 4, 2.3456),
(2.34559, 2, 5, 2.34559),
(2.34559, 2, 3, 2.346),
(2.9999, 2, 3, 3.000),
(2.9909, 2, 3, 2.991),
# Tests for Tick_size
(2.34559, 4, 0.0001, 2.3456),
(2.34559, 4, 0.00001, 2.34559),
(2.34559, 4, 0.001, 2.346),
(2.9999, 4, 0.001, 3.000),
(2.9909, 4, 0.001, 2.991),
(2.9909, 4, 0.005, 2.995),
(2.9973, 4, 0.005, 3.0),
(2.9977, 4, 0.005, 3.0),
(234.43, 4, 0.5, 234.5),
(234.53, 4, 0.5, 235.0),
(0.891534, 4, 0.0001, 0.8916),
])
def test_price_to_precision(default_conf, mocker, price, precision_mode, precision, expected):
'''
Test price to precision
'''
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': precision}}})
exchange = get_patched_exchange(mocker, default_conf, id="binance")
mocker.patch('freqtrade.exchange.Exchange.markets', markets)
# digits counting mode
# DECIMAL_PLACES = 2
# SIGNIFICANT_DIGITS = 3
# TICK_SIZE = 4
mocker.patch('freqtrade.exchange.Exchange.precisionMode',
PropertyMock(return_value=precision_mode))
amount = 2.34559
pair = 'ETH/BTC'
amount = exchange.symbol_amount_prec(pair, amount)
assert amount == 2.3455
def test_symbol_price_prec(default_conf, mocker):
'''
Test rounds up to 4 decimal places
'''
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': 4}}})
exchange = get_patched_exchange(mocker, default_conf, id="binance")
mocker.patch('freqtrade.exchange.Exchange.markets', markets)
price = 2.34559
pair = 'ETH/BTC'
price = exchange.symbol_price_prec(pair, price)
assert price == 2.3456
assert pytest.approx(exchange.price_to_precision(pair, price)) == expected
def test_set_sandbox(default_conf, mocker):
@@ -257,9 +302,10 @@ def test__load_markets(default_conf, mocker, caplog):
api_mock = MagicMock()
api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError("SomeError"))
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
Exchange(default_conf)
assert log_has('Unable to initialize markets. Reason: SomeError', caplog)
@@ -315,6 +361,44 @@ def test__reload_markets_exception(default_conf, mocker, caplog):
assert log_has_re(r"Could not reload markets.*", caplog)
@pytest.mark.parametrize("stake_currency", ['ETH', 'BTC', 'USDT'])
def test_validate_stake_currency(default_conf, stake_currency, mocker, caplog):
default_conf['stake_currency'] = stake_currency
api_mock = MagicMock()
type(api_mock).markets = PropertyMock(return_value={
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'},
})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
Exchange(default_conf)
def test_validate_stake_currency_error(default_conf, mocker, caplog):
default_conf['stake_currency'] = 'XRP'
api_mock = MagicMock()
type(api_mock).markets = PropertyMock(return_value={
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'},
})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
with pytest.raises(OperationalException,
match=r'XRP is not available as stake on .*'
'Available currencies are: BTC, ETH, USDT'):
Exchange(default_conf)
def test_get_quote_currencies(default_conf, mocker):
ex = get_patched_exchange(mocker, default_conf)
assert set(ex.get_quote_currencies()) == set(['USD', 'BTC', 'USDT'])
def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly
api_mock = MagicMock()
type(api_mock).markets = PropertyMock(return_value={
@@ -324,8 +408,9 @@ def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs d
type(api_mock).id = id_mock
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
Exchange(default_conf)
@@ -335,8 +420,9 @@ def test_validate_pairs_not_available(default_conf, mocker):
'XRP/BTC': {'inactive': True}
})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
with pytest.raises(OperationalException, match=r'not available'):
Exchange(default_conf)
@@ -349,8 +435,9 @@ def test_validate_pairs_exception(default_conf, mocker, caplog):
type(api_mock).markets = PropertyMock(return_value={})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available on Binance'):
Exchange(default_conf)
@@ -368,8 +455,9 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog):
'NEO/BTC': {'info': 'TestString'}, # info can also be a string ...
})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
Exchange(default_conf)
assert log_has(f"Pair XRP/BTC is restricted for some users on this exchange."
@@ -377,8 +465,11 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog):
f"on the exchange and eventually remove XRP/BTC from your whitelist.", caplog)
def test_validate_timeframes(default_conf, mocker):
default_conf["ticker_interval"] = "5m"
@pytest.mark.parametrize("timeframe", [
('5m'), ("1m"), ("15m"), ("1h")
])
def test_validate_timeframes(default_conf, mocker, timeframe):
default_conf["ticker_interval"] = timeframe
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
@@ -390,7 +481,8 @@ def test_validate_timeframes(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
Exchange(default_conf)
@@ -399,7 +491,8 @@ def test_validate_timeframes_failed(default_conf, mocker):
api_mock = MagicMock()
id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock
timeframes = PropertyMock(return_value={'1m': '1m',
timeframes = PropertyMock(return_value={'15s': '15s',
'1m': '1m',
'5m': '5m',
'15m': '15m',
'1h': '1h'})
@@ -411,6 +504,11 @@ def test_validate_timeframes_failed(default_conf, mocker):
with pytest.raises(OperationalException,
match=r"Invalid ticker interval '3m'. This exchange supports.*"):
Exchange(default_conf)
default_conf["ticker_interval"] = "15s"
with pytest.raises(OperationalException,
match=r"Timeframes < 1m are currently not supported by Freqtrade."):
Exchange(default_conf)
def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker):
@@ -424,7 +522,8 @@ def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
with pytest.raises(OperationalException,
match=r'The ccxt library does not provide the list of timeframes '
r'for the exchange ".*" and this exchange '
@@ -445,6 +544,7 @@ def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange._load_markets',
MagicMock(return_value={'timeframes': None}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
with pytest.raises(OperationalException,
match=r'The ccxt library does not provide the list of timeframes '
r'for the exchange ".*" and this exchange '
@@ -465,7 +565,8 @@ def test_validate_timeframes_not_in_config(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
Exchange(default_conf)
@@ -475,8 +576,9 @@ def test_validate_order_types(default_conf, mocker):
type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
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',
@@ -517,8 +619,9 @@ def test_validate_order_types_not_in_config(default_conf, mocker):
api_mock = MagicMock()
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
conf = copy.deepcopy(default_conf)
Exchange(conf)
@@ -529,9 +632,10 @@ def test_validate_required_startup_candles(default_conf, mocker, caplog):
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='Binance'))
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
default_conf['startup_candle_count'] = 20
ex = Exchange(default_conf)
@@ -596,8 +700,8 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
order = exchange.create_order(
@@ -637,8 +741,8 @@ def test_buy_prod(default_conf, mocker, exchange_name):
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
order = exchange.buy(pair='ETH/BTC', ordertype=order_type,
@@ -711,8 +815,8 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
order_type = 'limit'
@@ -773,8 +877,8 @@ def test_sell_prod(default_conf, mocker, exchange_name):
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
@@ -837,8 +941,8 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
})
api_mock.options = {}
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
order_type = 'limit'
@@ -1665,7 +1769,9 @@ def test_merge_ft_has_dict(default_conf, mocker):
_init_ccxt=MagicMock(return_value=MagicMock()),
_load_async_markets=MagicMock(),
validate_pairs=MagicMock(),
validate_timeframes=MagicMock())
validate_timeframes=MagicMock(),
validate_stakecurrency=MagicMock()
)
ex = Exchange(default_conf)
assert ex._ft_has == Exchange._ft_has_default

View File

@@ -21,8 +21,8 @@ def test_buy_kraken_trading_agreement(default_conf, mocker):
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
order = exchange.buy(pair='ETH/BTC', ordertype=order_type,
@@ -53,8 +53,8 @@ def test_sell_kraken_trading_agreement(default_conf, mocker):
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)

View File

@@ -325,105 +325,6 @@ def test_tickerdata_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
assert data['UNITTEST/BTC'].equals(data2['UNITTEST/BTC'])
def test_generate_text_table(default_conf, mocker):
patch_exchange(mocker)
default_conf['max_open_trades'] = 2
backtesting = Backtesting(default_conf)
results = pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC'],
'profit_percent': [0.1, 0.2],
'profit_abs': [0.2, 0.4],
'trade_duration': [10, 30],
'profit': [2, 0],
'loss': [0, 0]
}
)
result_str = (
'| pair | buy count | avg profit % | cum profit % | '
'tot profit BTC | tot profit % | avg duration | profit | loss |\n'
'|:--------|------------:|---------------:|---------------:|'
'-----------------:|---------------:|:---------------|---------:|-------:|\n'
'| ETH/BTC | 2 | 15.00 | 30.00 | '
'0.60000000 | 15.00 | 0:20:00 | 2 | 0 |\n'
'| TOTAL | 2 | 15.00 | 30.00 | '
'0.60000000 | 15.00 | 0:20:00 | 2 | 0 |'
)
assert backtesting._generate_text_table(data={'ETH/BTC': {}}, results=results) == result_str
def test_generate_text_table_sell_reason(default_conf, mocker):
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
results = pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
'profit_percent': [0.1, 0.2, -0.3],
'profit_abs': [0.2, 0.4, -0.5],
'trade_duration': [10, 30, 10],
'profit': [2, 0, 0],
'loss': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}
)
result_str = (
'| Sell Reason | Count | Profit | Loss |\n'
'|:--------------|--------:|---------:|-------:|\n'
'| roi | 2 | 2 | 0 |\n'
'| stop_loss | 1 | 0 | 1 |'
)
assert backtesting._generate_text_table_sell_reason(
data={'ETH/BTC': {}}, results=results) == result_str
def test_generate_text_table_strategyn(default_conf, mocker):
"""
Test Backtesting.generate_text_table_sell_reason() method
"""
patch_exchange(mocker)
default_conf['max_open_trades'] = 2
backtesting = Backtesting(default_conf)
results = {}
results['ETH/BTC'] = pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
'profit_percent': [0.1, 0.2, 0.3],
'profit_abs': [0.2, 0.4, 0.5],
'trade_duration': [10, 30, 10],
'profit': [2, 0, 0],
'loss': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}
)
results['LTC/BTC'] = pd.DataFrame(
{
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
'profit_percent': [0.4, 0.2, 0.3],
'profit_abs': [0.4, 0.4, 0.5],
'trade_duration': [15, 30, 15],
'profit': [4, 1, 0],
'loss': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}
)
result_str = (
'| Strategy | buy count | avg profit % | cum profit % '
'| tot profit BTC | tot profit % | avg duration | profit | loss |\n'
'|:-----------|------------:|---------------:|---------------:'
'|-----------------:|---------------:|:---------------|---------:|-------:|\n'
'| ETH/BTC | 3 | 20.00 | 60.00 '
'| 1.10000000 | 30.00 | 0:17:00 | 3 | 0 |\n'
'| LTC/BTC | 3 | 30.00 | 90.00 '
'| 1.30000000 | 45.00 | 0:20:00 | 3 | 0 |'
)
assert backtesting._generate_text_table_strategy(all_results=results) == result_str
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)
@@ -431,11 +332,8 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', MagicMock())
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.optimize.backtesting.Backtesting',
backtest=MagicMock(),
_generate_text_table=MagicMock(return_value='1'),
)
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock())
mocker.patch('freqtrade.optimize.backtesting.generate_text_table', MagicMock(return_value=1))
default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
default_conf['ticker_interval'] = '1m'
@@ -465,11 +363,8 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) ->
mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', MagicMock())
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.optimize.backtesting.Backtesting',
backtest=MagicMock(),
_generate_text_table=MagicMock(return_value='1'),
)
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock())
mocker.patch('freqtrade.optimize.backtesting.generate_text_table', MagicMock(return_value=1))
default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
default_conf['ticker_interval'] = "1m"
@@ -774,7 +669,8 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
patch_exchange(mocker)
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock())
mocker.patch('freqtrade.optimize.backtesting.Backtesting._generate_text_table', MagicMock())
mocker.patch('freqtrade.optimize.backtesting.generate_text_table', MagicMock())
patched_configuration_load_config_file(mocker, default_conf)
args = [
@@ -815,10 +711,9 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
backtestmock = MagicMock()
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
gen_table_mock = MagicMock()
mocker.patch('freqtrade.optimize.backtesting.Backtesting._generate_text_table', gen_table_mock)
mocker.patch('freqtrade.optimize.backtesting.generate_text_table', gen_table_mock)
gen_strattable_mock = MagicMock()
mocker.patch('freqtrade.optimize.backtesting.Backtesting._generate_text_table_strategy',
gen_strattable_mock)
mocker.patch('freqtrade.optimize.backtesting.generate_text_table_strategy', gen_strattable_mock)
patched_configuration_load_config_file(mocker, default_conf)
args = [

View File

@@ -3,7 +3,6 @@
from unittest.mock import MagicMock
from freqtrade.edge import PairInfo
from freqtrade.optimize import setup_configuration, start_edge
from freqtrade.optimize.edge_cli import EdgeCli
from freqtrade.state import RunMode
@@ -106,16 +105,3 @@ def test_edge_init_fee(mocker, edge_conf) -> None:
edge_cli = EdgeCli(edge_conf)
assert edge_cli.edge.fee == 0.1234
assert fee_mock.call_count == 0
def test_generate_edge_table(edge_conf, mocker):
patch_exchange(mocker)
edge_cli = EdgeCli(edge_conf)
results = {}
results['ETH/BTC'] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60)
assert edge_cli._generate_edge_table(results).count(':|') == 7
assert edge_cli._generate_edge_table(results).count('| ETH/BTC |') == 1
assert edge_cli._generate_edge_table(results).count(
'| risk reward ratio | required risk reward | expectancy |') == 1

View File

@@ -0,0 +1,108 @@
import pandas as pd
from freqtrade.edge import PairInfo
from freqtrade.optimize.optimize_reports import (
generate_edge_table, generate_text_table, generate_text_table_sell_reason,
generate_text_table_strategy)
from freqtrade.strategy.interface import SellType
def test_generate_text_table(default_conf, mocker):
results = pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC'],
'profit_percent': [0.1, 0.2],
'profit_abs': [0.2, 0.4],
'trade_duration': [10, 30],
'profit': [2, 0],
'loss': [0, 0]
}
)
result_str = (
'| pair | buy count | avg profit % | cum profit % | '
'tot profit BTC | tot profit % | avg duration | profit | loss |\n'
'|:--------|------------:|---------------:|---------------:|'
'-----------------:|---------------:|:---------------|---------:|-------:|\n'
'| ETH/BTC | 2 | 15.00 | 30.00 | '
'0.60000000 | 15.00 | 0:20:00 | 2 | 0 |\n'
'| TOTAL | 2 | 15.00 | 30.00 | '
'0.60000000 | 15.00 | 0:20:00 | 2 | 0 |'
)
assert generate_text_table(data={'ETH/BTC': {}},
stake_currency='BTC', max_open_trades=2,
results=results) == result_str
def test_generate_text_table_sell_reason(default_conf, mocker):
results = pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
'profit_percent': [0.1, 0.2, -0.1],
'profit_abs': [0.2, 0.4, -0.2],
'trade_duration': [10, 30, 10],
'profit': [2, 0, 0],
'loss': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}
)
result_str = (
'| Sell Reason | Count | Profit | Loss | Profit % |\n'
'|:--------------|--------:|---------:|-------:|-----------:|\n'
'| roi | 2 | 2 | 0 | 15 |\n'
'| stop_loss | 1 | 0 | 1 | -10 |'
)
assert generate_text_table_sell_reason(
data={'ETH/BTC': {}}, results=results) == result_str
def test_generate_text_table_strategy(default_conf, mocker):
results = {}
results['ETH/BTC'] = pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
'profit_percent': [0.1, 0.2, 0.3],
'profit_abs': [0.2, 0.4, 0.5],
'trade_duration': [10, 30, 10],
'profit': [2, 0, 0],
'loss': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}
)
results['LTC/BTC'] = pd.DataFrame(
{
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
'profit_percent': [0.4, 0.2, 0.3],
'profit_abs': [0.4, 0.4, 0.5],
'trade_duration': [15, 30, 15],
'profit': [4, 1, 0],
'loss': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}
)
result_str = (
'| Strategy | buy count | avg profit % | cum profit % '
'| tot profit BTC | tot profit % | avg duration | profit | loss |\n'
'|:-----------|------------:|---------------:|---------------:'
'|-----------------:|---------------:|:---------------|---------:|-------:|\n'
'| ETH/BTC | 3 | 20.00 | 60.00 '
'| 1.10000000 | 30.00 | 0:17:00 | 3 | 0 |\n'
'| LTC/BTC | 3 | 30.00 | 90.00 '
'| 1.30000000 | 45.00 | 0:20:00 | 3 | 0 |'
)
assert generate_text_table_strategy('BTC', 2, all_results=results) == result_str
def test_generate_edge_table(edge_conf, mocker):
results = {}
results['ETH/BTC'] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60)
assert generate_edge_table(results).count(':|') == 7
assert generate_edge_table(results).count('| ETH/BTC |') == 1
assert generate_edge_table(results).count(
'| risk reward ratio | required risk reward | expectancy |') == 1

View File

@@ -513,6 +513,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
),
get_fee=fee,
)
mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=1000)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))

View File

@@ -323,7 +323,7 @@ def test_load_dry_run(default_conf, mocker, config_value, expected, arglist) ->
configuration = Configuration(Arguments(arglist).get_parsed_arg())
validated_conf = configuration.load_config()
assert validated_conf.get('dry_run') is expected
assert validated_conf['dry_run'] is expected
def test_load_custom_strategy(default_conf, mocker) -> None:
@@ -723,6 +723,14 @@ def test_validate_default_conf(default_conf) -> None:
validate_config_schema(default_conf)
def test_validate_max_open_trades(default_conf):
default_conf['max_open_trades'] = float('inf')
default_conf['stake_amount'] = 'unlimited'
with pytest.raises(OperationalException, match='`max_open_trades` and `stake_amount` '
'cannot both be unlimited.'):
validate_config_consistency(default_conf)
def test_validate_tsl(default_conf):
default_conf['stoploss'] = 0.0
with pytest.raises(OperationalException, match='The config stoploss needs to be different '
@@ -1029,6 +1037,17 @@ def test_process_deprecated_setting_pairlists(mocker, default_conf, caplog):
assert log_has_re(r'DEPRECATED.*in pairlist is deprecated and must be moved*', caplog)
def test_process_deprecated_setting_edge(mocker, edge_conf, caplog):
patched_configuration_load_config_file(mocker, edge_conf)
edge_conf.update({'edge': {
'enabled': True,
'capital_available_percentage': 0.5,
}})
process_temporary_deprecated_settings(edge_conf)
assert log_has_re(r"DEPRECATED.*Using 'edge.capital_available_percentage'*", caplog)
def test_check_conflicting_settings(mocker, default_conf, caplog):
patched_configuration_load_config_file(mocker, default_conf)

View File

@@ -140,21 +140,65 @@ def test_get_trade_stake_amount(default_conf, ticker, mocker) -> None:
assert result == default_conf['stake_amount']
@pytest.mark.parametrize("amend_last,wallet,max_open,lsamr,expected", [
(False, 0.002, 2, 0.5, [0.001, None]),
(True, 0.002, 2, 0.5, [0.001, 0.00098]),
(False, 0.003, 3, 0.5, [0.001, 0.001, None]),
(True, 0.003, 3, 0.5, [0.001, 0.001, 0.00097]),
(False, 0.0022, 3, 0.5, [0.001, 0.001, 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,
amend_last, wallet, max_open, lsamr, expected) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2),
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee
)
default_conf['dry_run_wallet'] = wallet
default_conf['amend_last_stake_amount'] = amend_last
default_conf['last_stake_amount_min_ratio'] = lsamr
freqtrade = FreqtradeBot(default_conf)
for i in range(0, max_open):
if expected[i] is not None:
result = freqtrade.get_trade_stake_amount('ETH/BTC')
assert pytest.approx(result) == expected[i]
freqtrade.execute_buy('ETH/BTC', result)
else:
with pytest.raises(DependencyException):
freqtrade.get_trade_stake_amount('ETH/BTC')
def test_get_trade_stake_amount_no_stake_amount(default_conf, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
patch_wallet(mocker, free=default_conf['stake_amount'] * 0.5)
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
freqtrade.get_trade_stake_amount('ETH/BTC')
def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker,
@pytest.mark.parametrize("balance_ratio,result1", [
(1, 0.005),
(0.99, 0.00495),
(0.50, 0.0025),
])
def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_ratio, result1,
limit_buy_order, fee, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
patch_wallet(mocker, free=default_conf['stake_amount'])
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
@@ -164,32 +208,34 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker,
conf = deepcopy(default_conf)
conf['stake_amount'] = UNLIMITED_STAKE_AMOUNT
conf['dry_run_wallet'] = 0.01
conf['max_open_trades'] = 2
conf['tradable_balance_ratio'] = balance_ratio
freqtrade = FreqtradeBot(conf)
patch_get_signal(freqtrade)
# no open trades, order amount should be 'balance / max_open_trades'
result = freqtrade.get_trade_stake_amount('ETH/BTC')
assert result == default_conf['stake_amount'] / conf['max_open_trades']
assert result == result1
# create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)'
freqtrade.execute_buy('ETH/BTC', result)
result = freqtrade.get_trade_stake_amount('LTC/BTC')
assert result == default_conf['stake_amount'] / (conf['max_open_trades'] - 1)
assert result == result1
# create 2 trades, order amount should be None
freqtrade.execute_buy('LTC/BTC', result)
result = freqtrade.get_trade_stake_amount('XRP/BTC')
assert result is None
assert result == 0
# set max_open_trades = None, so do not trade
conf['max_open_trades'] = 0
freqtrade = FreqtradeBot(conf)
result = freqtrade.get_trade_stake_amount('NEO/BTC')
assert result is None
assert result == 0
def test_edge_called_in_process(mocker, edge_conf) -> None:
@@ -570,7 +616,7 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order,
patch_get_signal(freqtrade)
assert not freqtrade.create_trade('ETH/BTC')
assert freqtrade.get_trade_stake_amount('ETH/BTC') is None
assert freqtrade.get_trade_stake_amount('ETH/BTC') == 0
def test_enter_positions_no_pairs_left(default_conf, ticker, limit_buy_order, fee,
@@ -635,11 +681,15 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None:
@pytest.mark.parametrize("max_open", range(0, 5))
def test_create_trades_multiple_trades(default_conf, ticker,
fee, mocker, max_open) -> None:
@pytest.mark.parametrize("tradable_balance_ratio,modifier", [(1.0, 1), (0.99, 0.8), (0.5, 0.5)])
def test_create_trades_multiple_trades(default_conf, ticker, fee, mocker,
max_open, tradable_balance_ratio, modifier) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
default_conf['max_open_trades'] = max_open
default_conf['tradable_balance_ratio'] = tradable_balance_ratio
default_conf['dry_run_wallet'] = 0.001 * max_open
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
@@ -650,10 +700,11 @@ def test_create_trades_multiple_trades(default_conf, ticker,
patch_get_signal(freqtrade)
n = freqtrade.enter_positions()
assert n == max_open
trades = Trade.get_open_trades()
assert len(trades) == max_open
# Expected trades should be max_open * a modified value
# depending on the configured tradable_balance
assert n == max(int(max_open * modifier), 0)
assert len(trades) == max(int(max_open * modifier), 0)
def test_create_trades_preopen(default_conf, ticker, fee, mocker) -> None:
@@ -1267,6 +1318,14 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,
rate=0.00002344 * 0.95 * 0.99,
stop_price=0.00002344 * 0.95)
# price fell below stoploss, so dry-run sells trade.
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={
'bid': 0.00002144,
'ask': 0.00002146,
'last': 0.00002144
}))
assert freqtrade.handle_trade(trade) is True
def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, caplog,
limit_buy_order, limit_sell_order) -> None:
@@ -2384,8 +2443,8 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
get_fee=fee,
symbol_amount_prec=lambda s, x, y: y,
symbol_price_prec=lambda s, x, y: y,
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
stoploss_limit=stoploss_limit,
cancel_order=cancel_order,
)
@@ -2427,8 +2486,8 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, f
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
get_fee=fee,
symbol_amount_prec=lambda s, x, y: y,
symbol_price_prec=lambda s, x, y: y,
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
)
stoploss_limit = MagicMock(return_value={
@@ -2699,6 +2758,7 @@ def test__safe_sell_amount(default_conf, fee, caplog, mocker):
amount = 95.33
amount_wallet = 95.29
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=amount_wallet))
wallet_update = mocker.patch('freqtrade.wallets.Wallets.update')
trade = Trade(
pair='LTC/ETH',
amount=amount,
@@ -2711,8 +2771,15 @@ def test__safe_sell_amount(default_conf, fee, caplog, mocker):
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
wallet_update.reset_mock()
assert freqtrade._safe_sell_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_sell_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_sell_amount_error(default_conf, fee, caplog, mocker):
@@ -3622,6 +3689,7 @@ def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order,
# Initialize to 2 times stake amount
default_conf['dry_run_wallet'] = 0.002
default_conf['max_open_trades'] = 2
default_conf['tradable_balance_ratio'] = 1.0
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -3643,5 +3711,5 @@ def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order,
n = bot.enter_positions()
assert n == 0
assert log_has_re(r"Unable to create trade for XRP/BTC: "
r"Available balance \(0 BTC\) is lower than stake amount \(0.001 BTC\)",
r"Available balance \(0.0 BTC\) is lower than stake amount \(0.001 BTC\)",
caplog)

View File

@@ -1,10 +1,11 @@
from unittest.mock import MagicMock
import pytest
from freqtrade.persistence import Trade
from freqtrade.rpc.rpc import RPC
from freqtrade.strategy.interface import SellCheckTuple, SellType
from tests.conftest import get_patched_freqtradebot, patch_get_signal
from freqtrade.rpc.rpc import RPC
def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
@@ -57,8 +58,8 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
get_fee=fee,
symbol_amount_prec=lambda s, x, y: y,
symbol_price_prec=lambda s, x, y: y,
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
get_order=stoploss_order_mock,
cancel_order=cancel_order_mock,
)
@@ -96,8 +97,8 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
# Only order for 3rd trade needs to be cancelled
assert cancel_order_mock.call_count == 1
# Wallets should only be called once per sell cycle
assert wallets_mock.call_count == 1
# Wallets must be updated between stoploss cancellation and selling.
assert wallets_mock.call_count == 2
trade = trades[0]
assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value
@@ -112,13 +113,22 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
assert not trade.is_open
def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, mocker) -> None:
@pytest.mark.parametrize("balance_ratio,result1", [
(1, 200),
(0.99, 198),
])
def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, mocker, balance_ratio,
result1) -> None:
"""
Tests workflow
Tests workflow unlimited stake-amount
Buy 4 trades, forcebuy a 5th trade
Sell one trade, calculated stake amount should now be lower than before since
one trade was sold at a loss.
"""
default_conf['max_open_trades'] = 5
default_conf['forcebuy_enable'] = True
default_conf['stake_amount'] = 'unlimited'
default_conf['tradable_balance_ratio'] = balance_ratio
default_conf['dry_run_wallet'] = 1000
default_conf['exchange']['name'] = 'binance'
default_conf['telegram']['enabled'] = True
@@ -127,8 +137,8 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
get_fee=fee,
symbol_amount_prec=lambda s, x, y: y,
symbol_price_prec=lambda s, x, y: y,
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
)
mocker.patch.multiple(
@@ -159,13 +169,15 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
trades = Trade.query.all()
assert len(trades) == 4
assert freqtrade.get_trade_stake_amount('XRP/BTC') == result1
rpc._rpc_forcebuy('TKN/BTC', None)
trades = Trade.query.all()
assert len(trades) == 5
for trade in trades:
assert trade.stake_amount == 200
assert trade.stake_amount == result1
# Reset trade open order id's
trade.open_order_id = None
trades = Trade.get_open_trades()
@@ -177,6 +189,8 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
trades = Trade.get_open_trades()
# One trade sold
assert len(trades) == 4
# stake-amount should now be reduced, since one trade was sold at a loss.
assert freqtrade.get_trade_stake_amount('XRP/BTC') < result1
# Validate that balance of sold trade is not in dry-run balances anymore.
bals2 = freqtrade.wallets.get_all_balances()
assert bals != bals2

View File

@@ -13,6 +13,7 @@ from freqtrade.data.btanalysis import create_cum_profit, load_backtest_data
from freqtrade.exceptions import OperationalException
from freqtrade.plot.plot_utils import start_plot_dataframe, start_plot_profit
from freqtrade.plot.plotting import (add_indicators, add_profit,
create_plotconfig,
generate_candlestick_graph,
generate_plot_filename,
generate_profit_graph, init_plotscript,
@@ -66,8 +67,8 @@ def test_add_indicators(default_conf, testdatadir, caplog):
data = history.load_pair_history(pair=pair, timeframe='1m',
datadir=testdatadir, timerange=timerange)
indicators1 = ["ema10"]
indicators2 = ["macd"]
indicators1 = {"ema10": {}}
indicators2 = {"macd": {"color": "red"}}
# Generate buy/sell signals and indicators
strat = DefaultStrategy(default_conf)
@@ -86,9 +87,10 @@ def test_add_indicators(default_conf, testdatadir, caplog):
macd = find_trace_in_fig_data(figure.data, "macd")
assert isinstance(macd, go.Scatter)
assert macd.yaxis == "y3"
assert macd.line.color == "red"
# No indicator found
fig3 = add_indicators(fig=deepcopy(fig), row=3, indicators=['no_indicator'], data=data)
fig3 = add_indicators(fig=deepcopy(fig), row=3, indicators={'no_indicator': {}}, data=data)
assert fig == fig3
assert log_has_re(r'Indicator "no_indicator" ignored\..*', caplog)
@@ -108,18 +110,29 @@ def test_plot_trades(testdatadir, caplog):
figure = fig1.layout.figure
# Check buys - color, should be in first graph, ...
trade_buy = find_trace_in_fig_data(figure.data, "trade_buy")
trade_buy = find_trace_in_fig_data(figure.data, 'Trade buy')
assert isinstance(trade_buy, go.Scatter)
assert trade_buy.yaxis == 'y'
assert len(trades) == len(trade_buy.x)
assert trade_buy.marker.color == 'green'
assert trade_buy.marker.color == 'cyan'
assert trade_buy.marker.symbol == 'circle-open'
assert trade_buy.text[0] == '4.0%, roi, 15 min'
trade_sell = find_trace_in_fig_data(figure.data, "trade_sell")
trade_sell = find_trace_in_fig_data(figure.data, 'Sell - Profit')
assert isinstance(trade_sell, go.Scatter)
assert trade_sell.yaxis == 'y'
assert len(trades) == len(trade_sell.x)
assert trade_sell.marker.color == 'red'
assert trade_sell.text[0] == "4.0%, roi, 15 min"
assert len(trades.loc[trades['profitperc'] > 0]) == len(trade_sell.x)
assert trade_sell.marker.color == 'green'
assert trade_sell.marker.symbol == 'square-open'
assert trade_sell.text[0] == '4.0%, roi, 15 min'
trade_sell_loss = find_trace_in_fig_data(figure.data, 'Sell - Loss')
assert isinstance(trade_sell_loss, go.Scatter)
assert trade_sell_loss.yaxis == 'y'
assert len(trades.loc[trades['profitperc'] <= 0]) == len(trade_sell_loss.x)
assert trade_sell_loss.marker.color == 'red'
assert trade_sell_loss.marker.symbol == 'square-open'
assert trade_sell_loss.text[5] == '-10.4%, stop_loss, 720 min'
def test_generate_candlestick_graph_no_signals_no_trades(default_conf, mocker, testdatadir, caplog):
@@ -308,7 +321,7 @@ def test_load_and_plot_trades(default_conf, mocker, caplog, testdatadir):
"freqtrade.plot.plotting",
generate_candlestick_graph=candle_mock,
store_plot_file=store_mock
)
)
load_and_plot_trades(default_conf)
# Both mocks should be called once per pair
@@ -371,3 +384,47 @@ def test_plot_profit(default_conf, mocker, testdatadir, caplog):
assert profit_mock.call_args_list[0][0][0] == default_conf['pairs']
assert store_mock.call_args_list[0][1]['auto_open'] is True
@pytest.mark.parametrize("ind1,ind2,plot_conf,exp", [
# No indicators, use plot_conf
([], [], {},
{'main_plot': {'sma': {}, 'ema3': {}, 'ema5': {}},
'subplots': {'Other': {'macd': {}, 'macdsignal': {}}}}),
# use indicators
(['sma', 'ema3'], ['macd'], {},
{'main_plot': {'sma': {}, 'ema3': {}}, 'subplots': {'Other': {'macd': {}}}}),
# only main_plot - adds empty subplots
([], [], {'main_plot': {'sma': {}}},
{'main_plot': {'sma': {}}, 'subplots': {}}),
# Main and subplots
([], [], {'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}},
{'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}}),
# no main_plot, adds empty main_plot
([], [], {'subplots': {'RSI': {'rsi': {'color': 'red'}}}},
{'main_plot': {}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}}),
# indicator 1 / 2 should have prevelance
(['sma', 'ema3'], ['macd'],
{'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}},
{'main_plot': {'sma': {}, 'ema3': {}}, 'subplots': {'Other': {'macd': {}}}}
),
# indicator 1 - overrides plot_config main_plot
(['sma', 'ema3'], [],
{'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}},
{'main_plot': {'sma': {}, 'ema3': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}}
),
# indicator 2 - overrides plot_config subplots
([], ['macd', 'macd_signal'],
{'main_plot': {'sma': {}}, 'subplots': {'RSI': {'rsi': {'color': 'red'}}}},
{'main_plot': {'sma': {}}, 'subplots': {'Other': {'macd': {}, 'macd_signal': {}}}}
),
])
def test_create_plotconfig(ind1, ind2, plot_conf, exp):
res = create_plotconfig(ind1, ind2, plot_conf)
assert 'main_plot' in res
assert 'subplots' in res
assert isinstance(res['main_plot'], dict)
assert isinstance(res['subplots'], dict)
assert res == exp

View File

@@ -32,7 +32,7 @@ def test_sync_wallet_at_boot(mocker, default_conf):
assert freqtrade.wallets._wallets['GAS'].used == 0.0
assert freqtrade.wallets._wallets['GAS'].total == 0.260739
assert freqtrade.wallets.get_free('BNT') == 1.0
assert freqtrade.wallets._last_wallet_refresh > 0
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value={
@@ -61,6 +61,11 @@ def test_sync_wallet_at_boot(mocker, default_conf):
assert freqtrade.wallets.get_free('GAS') == 0.270739
assert freqtrade.wallets.get_used('GAS') == 0.1
assert freqtrade.wallets.get_total('GAS') == 0.260439
update_mock = mocker.patch('freqtrade.wallets.Wallets._update_live')
freqtrade.wallets.update(False)
assert update_mock.call_count == 0
freqtrade.wallets.update()
assert update_mock.call_count == 1
def test_sync_wallet_missing_data(mocker, default_conf):