merged lev-freqtradebot with lev-strat
This commit is contained in:
@@ -18,7 +18,7 @@ from freqtrade import constants
|
||||
from freqtrade.commands import Arguments
|
||||
from freqtrade.data.converter import ohlcv_to_dataframe
|
||||
from freqtrade.edge import Edge, PairInfo
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.enums import Collateral, RunMode, TradingMode
|
||||
from freqtrade.enums.signaltype import SignalDirection
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
@@ -90,7 +90,13 @@ def patched_configuration_load_config_file(mocker, config) -> None:
|
||||
)
|
||||
|
||||
|
||||
def patch_exchange(mocker, api_mock=None, id='binance', mock_markets=True) -> None:
|
||||
def patch_exchange(
|
||||
mocker,
|
||||
api_mock=None,
|
||||
id='binance',
|
||||
mock_markets=True,
|
||||
mock_supported_modes=True
|
||||
) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock(return_value={}))
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
|
||||
@@ -99,10 +105,22 @@ def patch_exchange(mocker, api_mock=None, id='binance', mock_markets=True) -> No
|
||||
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()))
|
||||
|
||||
if mock_supported_modes:
|
||||
mocker.patch(
|
||||
f'freqtrade.exchange.{id.capitalize()}._supported_trading_mode_collateral_pairs',
|
||||
PropertyMock(return_value=[
|
||||
(TradingMode.MARGIN, Collateral.CROSS),
|
||||
(TradingMode.MARGIN, Collateral.ISOLATED),
|
||||
(TradingMode.FUTURES, Collateral.CROSS),
|
||||
(TradingMode.FUTURES, Collateral.ISOLATED)
|
||||
])
|
||||
)
|
||||
|
||||
if api_mock:
|
||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||
else:
|
||||
@@ -110,8 +128,8 @@ def patch_exchange(mocker, api_mock=None, id='binance', mock_markets=True) -> No
|
||||
|
||||
|
||||
def get_patched_exchange(mocker, config, api_mock=None, id='binance',
|
||||
mock_markets=True) -> Exchange:
|
||||
patch_exchange(mocker, api_mock, id, mock_markets)
|
||||
mock_markets=True, mock_supported_modes=True) -> Exchange:
|
||||
patch_exchange(mocker, api_mock, id, mock_markets, mock_supported_modes)
|
||||
config['exchange']['name'] = id
|
||||
try:
|
||||
exchange = ExchangeResolver.load_exchange(id, config)
|
||||
|
@@ -5,7 +5,7 @@ from unittest.mock import MagicMock, PropertyMock
|
||||
import ccxt
|
||||
import pytest
|
||||
|
||||
from freqtrade.enums import TradingMode
|
||||
from freqtrade.enums import Collateral, TradingMode
|
||||
from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException
|
||||
from tests.conftest import get_mock_coro, get_patched_exchange, log_has_re
|
||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
@@ -48,13 +48,20 @@ def test_stoploss_order_binance(
|
||||
amount=1,
|
||||
stop_price=190,
|
||||
side=side,
|
||||
order_types={'stoploss_on_exchange_limit_ratio': 1.05}
|
||||
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio}
|
||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
||||
order_types=order_types, side=side)
|
||||
order = exchange.stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
stop_price=220,
|
||||
order_types=order_types,
|
||||
side=side,
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
@@ -71,17 +78,31 @@ def test_stoploss_order_binance(
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side)
|
||||
exchange.stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
stop_price=220,
|
||||
order_types={},
|
||||
side=side,
|
||||
leverage=1.0)
|
||||
|
||||
with pytest.raises(InvalidOrderException):
|
||||
api_mock.create_order = MagicMock(
|
||||
side_effect=ccxt.InvalidOrder("binance Order would trigger immediately."))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side)
|
||||
exchange.stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
stop_price=220,
|
||||
order_types={},
|
||||
side=side,
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance",
|
||||
"stoploss", "create_order", retries=1,
|
||||
pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side)
|
||||
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
|
||||
side=side, leverage=1.0)
|
||||
|
||||
|
||||
def test_stoploss_order_dry_run_binance(default_conf, mocker):
|
||||
@@ -94,12 +115,25 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell",
|
||||
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
|
||||
order = exchange.stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
stop_price=190,
|
||||
side="sell",
|
||||
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
|
||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
|
||||
order = exchange.stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
stop_price=220,
|
||||
order_types={},
|
||||
side="sell",
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
@@ -194,6 +228,9 @@ def test_fill_leverage_brackets_binance(default_conf, mocker):
|
||||
[1000000.0, 0.5]],
|
||||
|
||||
})
|
||||
default_conf['dry_run'] = False
|
||||
default_conf['trading_mode'] = TradingMode.FUTURES
|
||||
default_conf['collateral'] = Collateral.ISOLATED
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance")
|
||||
exchange.fill_leverage_brackets()
|
||||
|
||||
@@ -236,12 +273,59 @@ def test_fill_leverage_brackets_binance(default_conf, mocker):
|
||||
)
|
||||
|
||||
|
||||
def test_fill_leverage_brackets_binance_dryrun(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
default_conf['trading_mode'] = TradingMode.FUTURES
|
||||
default_conf['collateral'] = Collateral.ISOLATED
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance")
|
||||
exchange.fill_leverage_brackets()
|
||||
|
||||
leverage_brackets = {
|
||||
"1000SHIB/USDT": [
|
||||
[0.0, 0.01],
|
||||
[5000.0, 0.025],
|
||||
[25000.0, 0.05],
|
||||
[100000.0, 0.1],
|
||||
[250000.0, 0.125],
|
||||
[1000000.0, 0.5]
|
||||
],
|
||||
"1INCH/USDT": [
|
||||
[0.0, 0.012],
|
||||
[5000.0, 0.025],
|
||||
[25000.0, 0.05],
|
||||
[100000.0, 0.1],
|
||||
[250000.0, 0.125],
|
||||
[1000000.0, 0.5]
|
||||
],
|
||||
"AAVE/USDT": [
|
||||
[0.0, 0.01],
|
||||
[50000.0, 0.02],
|
||||
[250000.0, 0.05],
|
||||
[1000000.0, 0.1],
|
||||
[2000000.0, 0.125],
|
||||
[5000000.0, 0.1665],
|
||||
[10000000.0, 0.25]
|
||||
],
|
||||
"ADA/BUSD": [
|
||||
[0.0, 0.025],
|
||||
[100000.0, 0.05],
|
||||
[500000.0, 0.1],
|
||||
[1000000.0, 0.15],
|
||||
[2000000.0, 0.25],
|
||||
[5000000.0, 0.5]
|
||||
]
|
||||
}
|
||||
|
||||
for key, value in leverage_brackets.items():
|
||||
assert exchange._leverage_brackets[key] == value
|
||||
|
||||
|
||||
def test__set_leverage_binance(mocker, default_conf):
|
||||
|
||||
api_mock = MagicMock()
|
||||
api_mock.set_leverage = MagicMock()
|
||||
type(api_mock).has = PropertyMock(return_value={'setLeverage': True})
|
||||
|
||||
default_conf['dry_run'] = False
|
||||
exchange = get_patched_exchange(mocker, default_conf, id="binance")
|
||||
exchange._set_leverage(3.0, trading_mode=TradingMode.MARGIN)
|
||||
|
||||
@@ -288,3 +372,15 @@ async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog):
|
||||
assert exchange._api_async.fetch_ohlcv.call_count == 2
|
||||
assert res == ohlcv
|
||||
assert log_has_re(r"Candle-data for ETH/BTC available starting with .*", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("trading_mode,collateral,config", [
|
||||
("", "", {}),
|
||||
("margin", "cross", {"options": {"defaultType": "margin"}}),
|
||||
("futures", "isolated", {"options": {"defaultType": "future"}}),
|
||||
])
|
||||
def test__ccxt_config(default_conf, mocker, trading_mode, collateral, config):
|
||||
default_conf['trading_mode'] = trading_mode
|
||||
default_conf['collateral'] = collateral
|
||||
exchange = get_patched_exchange(mocker, default_conf, id="binance")
|
||||
assert exchange._ccxt_config == config
|
||||
|
@@ -132,6 +132,7 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog):
|
||||
|
||||
assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog)
|
||||
assert ex._api.headers == {'hello': 'world'}
|
||||
assert ex._ccxt_config == {}
|
||||
Exchange._headers = {}
|
||||
|
||||
# TODO-lev: Test with options
|
||||
@@ -403,7 +404,6 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
|
||||
# With Leverage
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss, 3.0)
|
||||
assert isclose(result, expected_result/3)
|
||||
# TODO-lev: Min stake for base, kraken and ftx
|
||||
|
||||
# min amount is set
|
||||
markets["ETH/BTC"]["limits"] = {
|
||||
@@ -420,27 +420,21 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
|
||||
# With Leverage
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0)
|
||||
assert isclose(result, expected_result/5)
|
||||
# TODO-lev: Min stake for base, kraken and ftx
|
||||
|
||||
# min amount and cost are set (cost is minimal)
|
||||
markets["ETH/BTC"]["limits"] = {
|
||||
'cost': {'min': 2},
|
||||
'amount': {'min': 2}
|
||||
}
|
||||
mocker.patch(
|
||||
'freqtrade.exchange.Exchange.markets',
|
||||
PropertyMock(return_value=markets)
|
||||
)
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
||||
expected_result = max(2, 2 * 2) * (1+0.05) / (1-abs(stoploss))
|
||||
result=exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
||||
expected_result=max(2, 2 * 2) * (1+0.05) / (1-abs(stoploss))
|
||||
assert isclose(result, expected_result)
|
||||
# With Leverage
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 10)
|
||||
result=exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 10)
|
||||
assert isclose(result, expected_result/10)
|
||||
# TODO-lev: Min stake for base, kraken and ftx
|
||||
|
||||
# min amount and cost are set (amount is minial)
|
||||
markets["ETH/BTC"]["limits"] = {
|
||||
markets["ETH/BTC"]["limits"]={
|
||||
'cost': {'min': 8},
|
||||
'amount': {'min': 2}
|
||||
}
|
||||
@@ -448,39 +442,28 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
|
||||
'freqtrade.exchange.Exchange.markets',
|
||||
PropertyMock(return_value=markets)
|
||||
)
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
||||
expected_result = max(8, 2 * 2) * (1+0.05) / (1-abs(stoploss))
|
||||
result=exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
|
||||
expected_result=max(8, 2 * 2) * (1+0.05) / (1-abs(stoploss))
|
||||
assert isclose(result, expected_result)
|
||||
# With Leverage
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 7.0)
|
||||
result=exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 7.0)
|
||||
assert isclose(result, expected_result/7.0)
|
||||
# TODO-lev: Min stake for base, kraken and ftx
|
||||
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4)
|
||||
expected_result = max(8, 2 * 2) * 1.5
|
||||
result=exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4)
|
||||
expected_result=max(8, 2 * 2) * 1.5
|
||||
assert isclose(result, expected_result)
|
||||
# With Leverage
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4, 8.0)
|
||||
result=exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4, 8.0)
|
||||
assert isclose(result, expected_result/8.0)
|
||||
# TODO-lev: Min stake for base, kraken and ftx
|
||||
|
||||
# Really big stoploss
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1)
|
||||
expected_result = max(8, 2 * 2) * 1.5
|
||||
assert isclose(result, expected_result)
|
||||
# With Leverage
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0)
|
||||
result=exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0)
|
||||
assert isclose(result, expected_result/12)
|
||||
# TODO-lev: Min stake for base, kraken and ftx
|
||||
|
||||
|
||||
def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None:
|
||||
exchange = get_patched_exchange(mocker, default_conf, id="binance")
|
||||
stoploss = -0.05
|
||||
markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}}
|
||||
stoploss=-0.05
|
||||
markets={'ETH/BTC': {'symbol': 'ETH/BTC'}}
|
||||
|
||||
# Real Binance data
|
||||
markets["ETH/BTC"]["limits"] = {
|
||||
markets["ETH/BTC"]["limits"]={
|
||||
'cost': {'min': 0.0001},
|
||||
'amount': {'min': 0.001}
|
||||
}
|
||||
@@ -488,28 +471,27 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None:
|
||||
'freqtrade.exchange.Exchange.markets',
|
||||
PropertyMock(return_value=markets)
|
||||
)
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss)
|
||||
expected_result = max(0.0001, 0.001 * 0.020405) * (1+0.05) / (1-abs(stoploss))
|
||||
result=exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss)
|
||||
expected_result=max(0.0001, 0.001 * 0.020405) * (1+0.05) / (1-abs(stoploss))
|
||||
assert round(result, 8) == round(expected_result, 8)
|
||||
result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0)
|
||||
result=exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0)
|
||||
assert round(result, 8) == round(expected_result/3, 8)
|
||||
# TODO-lev: Min stake for base, kraken and ftx
|
||||
|
||||
|
||||
def test_set_sandbox(default_conf, mocker):
|
||||
"""
|
||||
Test working scenario
|
||||
"""
|
||||
api_mock = MagicMock()
|
||||
api_mock.load_markets = MagicMock(return_value={
|
||||
api_mock=MagicMock()
|
||||
api_mock.load_markets=MagicMock(return_value = {
|
||||
'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': ''
|
||||
})
|
||||
url_mock = PropertyMock(return_value={'test': "api-public.sandbox.gdax.com",
|
||||
url_mock=PropertyMock(return_value = {'test': "api-public.sandbox.gdax.com",
|
||||
'api': 'https://api.gdax.com'})
|
||||
type(api_mock).urls = url_mock
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
liveurl = exchange._api.urls['api']
|
||||
default_conf['exchange']['sandbox'] = True
|
||||
type(api_mock).urls=url_mock
|
||||
exchange=get_patched_exchange(mocker, default_conf, api_mock)
|
||||
liveurl=exchange._api.urls['api']
|
||||
default_conf['exchange']['sandbox']=True
|
||||
exchange.set_sandbox(exchange._api, default_conf['exchange'], 'Logname')
|
||||
assert exchange._api.urls['api'] != liveurl
|
||||
|
||||
@@ -518,16 +500,16 @@ def test_set_sandbox_exception(default_conf, mocker):
|
||||
"""
|
||||
Test Fail scenario
|
||||
"""
|
||||
api_mock = MagicMock()
|
||||
api_mock.load_markets = MagicMock(return_value={
|
||||
api_mock=MagicMock()
|
||||
api_mock.load_markets=MagicMock(return_value = {
|
||||
'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': ''
|
||||
})
|
||||
url_mock = PropertyMock(return_value={'api': 'https://api.gdax.com'})
|
||||
type(api_mock).urls = url_mock
|
||||
url_mock=PropertyMock(return_value = {'api': 'https://api.gdax.com'})
|
||||
type(api_mock).urls=url_mock
|
||||
|
||||
with pytest.raises(OperationalException, match=r'does not provide a sandbox api'):
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
default_conf['exchange']['sandbox'] = True
|
||||
with pytest.raises(OperationalException, match = r'does not provide a sandbox api'):
|
||||
exchange=get_patched_exchange(mocker, default_conf, api_mock)
|
||||
default_conf['exchange']['sandbox']=True
|
||||
exchange.set_sandbox(exchange._api, default_conf['exchange'], 'Logname')
|
||||
|
||||
|
||||
@@ -537,13 +519,13 @@ def test__load_async_markets(default_conf, mocker, caplog):
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
||||
mocker.patch('freqtrade.exchange.Exchange._load_markets')
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||
exchange = Exchange(default_conf)
|
||||
exchange._api_async.load_markets = get_mock_coro(None)
|
||||
exchange=Exchange(default_conf)
|
||||
exchange._api_async.load_markets=get_mock_coro(None)
|
||||
exchange._load_async_markets()
|
||||
assert exchange._api_async.load_markets.call_count == 1
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
exchange._api_async.load_markets = Mock(side_effect=ccxt.BaseError("deadbeef"))
|
||||
exchange._api_async.load_markets=Mock(side_effect = ccxt.BaseError("deadbeef"))
|
||||
exchange._load_async_markets()
|
||||
|
||||
assert log_has('Could not load async markets. Reason: deadbeef', caplog)
|
||||
@@ -551,8 +533,8 @@ def test__load_async_markets(default_conf, mocker, caplog):
|
||||
|
||||
def test__load_markets(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
api_mock = MagicMock()
|
||||
api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError("SomeError"))
|
||||
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')
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
||||
@@ -561,28 +543,28 @@ def test__load_markets(default_conf, mocker, caplog):
|
||||
Exchange(default_conf)
|
||||
assert log_has('Unable to initialize markets.', caplog)
|
||||
|
||||
expected_return = {'ETH/BTC': 'available'}
|
||||
api_mock = MagicMock()
|
||||
api_mock.load_markets = MagicMock(return_value=expected_return)
|
||||
expected_return={'ETH/BTC': 'available'}
|
||||
api_mock=MagicMock()
|
||||
api_mock.load_markets=MagicMock(return_value = expected_return)
|
||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||
default_conf['exchange']['pair_whitelist'] = ['ETH/BTC']
|
||||
ex = Exchange(default_conf)
|
||||
default_conf['exchange']['pair_whitelist']=['ETH/BTC']
|
||||
ex=Exchange(default_conf)
|
||||
|
||||
assert ex.markets == expected_return
|
||||
|
||||
|
||||
def test_reload_markets(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.DEBUG)
|
||||
initial_markets = {'ETH/BTC': {}}
|
||||
updated_markets = {'ETH/BTC': {}, "LTC/BTC": {}}
|
||||
initial_markets={'ETH/BTC': {}}
|
||||
updated_markets={'ETH/BTC': {}, "LTC/BTC": {}}
|
||||
|
||||
api_mock = MagicMock()
|
||||
api_mock.load_markets = MagicMock(return_value=initial_markets)
|
||||
default_conf['exchange']['markets_refresh_interval'] = 10
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance",
|
||||
mock_markets=False)
|
||||
exchange._load_async_markets = MagicMock()
|
||||
exchange._last_markets_refresh = arrow.utcnow().int_timestamp
|
||||
api_mock=MagicMock()
|
||||
api_mock.load_markets=MagicMock(return_value = initial_markets)
|
||||
default_conf['exchange']['markets_refresh_interval']=10
|
||||
exchange=get_patched_exchange(mocker, default_conf, api_mock, id = "binance",
|
||||
mock_markets = False)
|
||||
exchange._load_async_markets=MagicMock()
|
||||
exchange._last_markets_refresh=arrow.utcnow().int_timestamp
|
||||
|
||||
assert exchange.markets == initial_markets
|
||||
|
||||
@@ -591,9 +573,9 @@ def test_reload_markets(default_conf, mocker, caplog):
|
||||
assert exchange.markets == initial_markets
|
||||
assert exchange._load_async_markets.call_count == 0
|
||||
|
||||
api_mock.load_markets = MagicMock(return_value=updated_markets)
|
||||
api_mock.load_markets=MagicMock(return_value = updated_markets)
|
||||
# more than 10 minutes have passed, reload is executed
|
||||
exchange._last_markets_refresh = arrow.utcnow().int_timestamp - 15 * 60
|
||||
exchange._last_markets_refresh=arrow.utcnow().int_timestamp - 15 * 60
|
||||
exchange.reload_markets()
|
||||
assert exchange.markets == updated_markets
|
||||
assert exchange._load_async_markets.call_count == 1
|
||||
@@ -603,10 +585,10 @@ def test_reload_markets(default_conf, mocker, caplog):
|
||||
def test_reload_markets_exception(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
api_mock = MagicMock()
|
||||
api_mock.load_markets = MagicMock(side_effect=ccxt.NetworkError("LoadError"))
|
||||
default_conf['exchange']['markets_refresh_interval'] = 10
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance")
|
||||
api_mock=MagicMock()
|
||||
api_mock.load_markets=MagicMock(side_effect = ccxt.NetworkError("LoadError"))
|
||||
default_conf['exchange']['markets_refresh_interval']=10
|
||||
exchange=get_patched_exchange(mocker, default_conf, api_mock, id = "binance")
|
||||
|
||||
# less than 10 minutes have passed, no reload
|
||||
exchange.reload_markets()
|
||||
@@ -614,11 +596,11 @@ 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'])
|
||||
@ pytest.mark.parametrize("stake_currency", ['ETH', 'BTC', 'USDT'])
|
||||
def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog):
|
||||
default_conf['stake_currency'] = stake_currency
|
||||
api_mock = MagicMock()
|
||||
type(api_mock).load_markets = MagicMock(return_value={
|
||||
default_conf['stake_currency']=stake_currency
|
||||
api_mock=MagicMock()
|
||||
type(api_mock).load_markets=MagicMock(return_value = {
|
||||
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
|
||||
'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'},
|
||||
})
|
||||
@@ -630,9 +612,9 @@ def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog):
|
||||
|
||||
|
||||
def test_validate_stakecurrency_error(default_conf, mocker, caplog):
|
||||
default_conf['stake_currency'] = 'XRP'
|
||||
api_mock = MagicMock()
|
||||
type(api_mock).load_markets = MagicMock(return_value={
|
||||
default_conf['stake_currency']='XRP'
|
||||
api_mock=MagicMock()
|
||||
type(api_mock).load_markets=MagicMock(return_value = {
|
||||
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
|
||||
'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'},
|
||||
})
|
||||
@@ -1004,7 +986,13 @@ def test_create_dry_run_order(default_conf, mocker, side, exchange_name):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
|
||||
order = exchange.create_dry_run_order(
|
||||
pair='ETH/BTC', ordertype='limit', side=side, amount=1, rate=200)
|
||||
pair='ETH/BTC',
|
||||
ordertype='limit',
|
||||
side=side,
|
||||
amount=1,
|
||||
rate=200,
|
||||
leverage=1.0
|
||||
)
|
||||
assert 'id' in order
|
||||
assert f'dry_run_{side}_' in order["id"]
|
||||
assert order["side"] == side
|
||||
@@ -1027,7 +1015,13 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, startprice,
|
||||
)
|
||||
|
||||
order = exchange.create_dry_run_order(
|
||||
pair='LTC/USDT', ordertype='limit', side=side, amount=1, rate=startprice)
|
||||
pair='LTC/USDT',
|
||||
ordertype='limit',
|
||||
side=side,
|
||||
amount=1,
|
||||
rate=startprice,
|
||||
leverage=1.0
|
||||
)
|
||||
assert order_book_l2_usd.call_count == 1
|
||||
assert 'id' in order
|
||||
assert f'dry_run_{side}_' in order["id"]
|
||||
@@ -1073,7 +1067,13 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou
|
||||
)
|
||||
|
||||
order = exchange.create_dry_run_order(
|
||||
pair='LTC/USDT', ordertype='market', side=side, amount=amount, rate=rate)
|
||||
pair='LTC/USDT',
|
||||
ordertype='market',
|
||||
side=side,
|
||||
amount=amount,
|
||||
rate=rate,
|
||||
leverage=1.0
|
||||
)
|
||||
assert 'id' in order
|
||||
assert f'dry_run_{side}_' in order["id"]
|
||||
assert order["side"] == side
|
||||
@@ -1083,10 +1083,7 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou
|
||||
assert round(order["average"], 4) == round(endprice, 4)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("side", [
|
||||
("buy"),
|
||||
("sell")
|
||||
])
|
||||
@pytest.mark.parametrize("side", ["buy", "sell"])
|
||||
@pytest.mark.parametrize("ordertype,rate,marketprice", [
|
||||
("market", None, None),
|
||||
("market", 200, True),
|
||||
@@ -1108,9 +1105,17 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
|
||||
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)
|
||||
exchange._set_leverage = MagicMock()
|
||||
exchange.set_margin_mode = MagicMock()
|
||||
|
||||
order = exchange.create_order(
|
||||
pair='ETH/BTC', ordertype=ordertype, side=side, amount=1, rate=200)
|
||||
pair='ETH/BTC',
|
||||
ordertype=ordertype,
|
||||
side=side,
|
||||
amount=1,
|
||||
rate=200,
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
@@ -1120,6 +1125,21 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
|
||||
assert api_mock.create_order.call_args[0][2] == side
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] is rate
|
||||
assert exchange._set_leverage.call_count == 0
|
||||
assert exchange.set_margin_mode.call_count == 0
|
||||
|
||||
exchange.trading_mode = TradingMode.FUTURES
|
||||
order = exchange.create_order(
|
||||
pair='ETH/BTC',
|
||||
ordertype=ordertype,
|
||||
side=side,
|
||||
amount=1,
|
||||
rate=200,
|
||||
leverage=3.0
|
||||
)
|
||||
|
||||
assert exchange._set_leverage.call_count == 1
|
||||
assert exchange.set_margin_mode.call_count == 1
|
||||
|
||||
|
||||
def test_buy_dry_run(default_conf, mocker):
|
||||
@@ -2658,7 +2678,14 @@ def test_get_fee(default_conf, mocker, exchange_name):
|
||||
def test_stoploss_order_unsupported_exchange(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='bittrex')
|
||||
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
|
||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
|
||||
exchange.stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
stop_price=220,
|
||||
order_types={},
|
||||
side="sell",
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
|
||||
exchange.stoploss_adjust(1, {}, side="sell")
|
||||
@@ -3006,7 +3033,6 @@ def test_calculate_fee_rate(mocker, default_conf, order, expected) -> None:
|
||||
(3, 5, 5),
|
||||
(4, 5, 2),
|
||||
(5, 5, 1),
|
||||
|
||||
])
|
||||
def test_calculate_backoff(retrycount, max_retries, expected):
|
||||
assert calculate_backoff(retrycount, max_retries) == expected
|
||||
@@ -3018,7 +3044,7 @@ def test_calculate_backoff(retrycount, max_retries, expected):
|
||||
(20.0, 5.0, 4.0),
|
||||
(100.0, 100.0, 1.0)
|
||||
])
|
||||
def test_apply_leverage_to_stake_amount(
|
||||
def test_get_stake_amount_considering_leverage(
|
||||
exchange,
|
||||
stake_amount,
|
||||
leverage,
|
||||
@@ -3027,7 +3053,8 @@ def test_apply_leverage_to_stake_amount(
|
||||
default_conf
|
||||
):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange)
|
||||
assert exchange._apply_leverage_to_stake_amount(stake_amount, leverage) == min_stake_with_lev
|
||||
assert exchange._get_stake_amount_considering_leverage(
|
||||
stake_amount, leverage) == min_stake_with_lev
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name,trading_mode", [
|
||||
@@ -3040,6 +3067,7 @@ def test__set_leverage(mocker, default_conf, exchange_name, trading_mode):
|
||||
api_mock = MagicMock()
|
||||
api_mock.set_leverage = MagicMock()
|
||||
type(api_mock).has = PropertyMock(return_value={'setLeverage': True})
|
||||
default_conf['dry_run'] = False
|
||||
|
||||
ccxt_exceptionhandlers(
|
||||
mocker,
|
||||
@@ -3063,6 +3091,7 @@ def test_set_margin_mode(mocker, default_conf, collateral):
|
||||
api_mock = MagicMock()
|
||||
api_mock.set_margin_mode = MagicMock()
|
||||
type(api_mock).has = PropertyMock(return_value={'setMarginMode': True})
|
||||
default_conf['dry_run'] = False
|
||||
|
||||
ccxt_exceptionhandlers(
|
||||
mocker,
|
||||
@@ -3117,7 +3146,8 @@ def test_validate_trading_mode_and_collateral(
|
||||
collateral,
|
||||
exception_thrown
|
||||
):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
exchange = get_patched_exchange(
|
||||
mocker, default_conf, id=exchange_name, mock_supported_modes=False)
|
||||
if (exception_thrown):
|
||||
with pytest.raises(OperationalException):
|
||||
exchange.validate_trading_mode_and_collateral(trading_mode, collateral)
|
||||
|
@@ -37,8 +37,14 @@ def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitrati
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
||||
|
||||
# stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders
|
||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side=side,
|
||||
order_types={'stoploss_on_exchange_limit_ratio': exchangelimitratio})
|
||||
order = exchange.stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
stop_price=190,
|
||||
side=side,
|
||||
order_types={'stoploss_on_exchange_limit_ratio': exchangelimitratio},
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
|
||||
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
|
||||
@@ -52,7 +58,14 @@ def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitrati
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
|
||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side)
|
||||
order = exchange.stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
stop_price=220,
|
||||
order_types={},
|
||||
side=side,
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
@@ -65,8 +78,13 @@ def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitrati
|
||||
assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
||||
order_types={'stoploss': 'limit'}, side=side)
|
||||
order = exchange.stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
stop_price=220,
|
||||
order_types={'stoploss': 'limit'}, side=side,
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
@@ -83,17 +101,32 @@ def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitrati
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side)
|
||||
exchange.stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
stop_price=220,
|
||||
order_types={},
|
||||
side=side,
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
with pytest.raises(InvalidOrderException):
|
||||
api_mock.create_order = MagicMock(
|
||||
side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately."))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side)
|
||||
exchange.stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
stop_price=220,
|
||||
order_types={},
|
||||
side=side,
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx",
|
||||
"stoploss", "create_order", retries=1,
|
||||
pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side)
|
||||
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
|
||||
side=side, leverage=1.0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('side', [("sell"), ("buy")])
|
||||
@@ -107,7 +140,14 @@ def test_stoploss_order_dry_run_ftx(default_conf, mocker, side):
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
|
||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side)
|
||||
order = exchange.stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
stop_price=220,
|
||||
order_types={},
|
||||
side=side,
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
@@ -228,26 +268,3 @@ def test_fill_leverage_brackets_ftx(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id="ftx")
|
||||
exchange.fill_leverage_brackets()
|
||||
assert exchange._leverage_brackets == {}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("trading_mode", [
|
||||
(TradingMode.MARGIN),
|
||||
(TradingMode.FUTURES)
|
||||
])
|
||||
def test__set_leverage(mocker, default_conf, trading_mode):
|
||||
|
||||
api_mock = MagicMock()
|
||||
api_mock.set_leverage = MagicMock()
|
||||
type(api_mock).has = PropertyMock(return_value={'setLeverage': True})
|
||||
|
||||
ccxt_exceptionhandlers(
|
||||
mocker,
|
||||
default_conf,
|
||||
api_mock,
|
||||
"ftx",
|
||||
"_set_leverage",
|
||||
"set_leverage",
|
||||
pair="XRP/USDT",
|
||||
leverage=5.0,
|
||||
trading_mode=trading_mode
|
||||
)
|
||||
|
@@ -197,7 +197,9 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr
|
||||
order_types={
|
||||
'stoploss': ordertype,
|
||||
'stoploss_on_exchange_limit_ratio': 0.99
|
||||
})
|
||||
},
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
@@ -221,17 +223,32 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side)
|
||||
exchange.stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
stop_price=220,
|
||||
order_types={},
|
||||
side=side,
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
with pytest.raises(InvalidOrderException):
|
||||
api_mock.create_order = MagicMock(
|
||||
side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately."))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side)
|
||||
exchange.stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
stop_price=220,
|
||||
order_types={},
|
||||
side=side,
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken",
|
||||
"stoploss", "create_order", retries=1,
|
||||
pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side)
|
||||
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
|
||||
side=side, leverage=1.0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('side', ['buy', 'sell'])
|
||||
@@ -245,7 +262,14 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker, side):
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
|
||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side)
|
||||
order = exchange.stoploss(
|
||||
pair='ETH/BTC',
|
||||
amount=1,
|
||||
stop_price=220,
|
||||
order_types={},
|
||||
side=side,
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
@@ -307,15 +331,3 @@ def test_fill_leverage_brackets_kraken(default_conf, mocker):
|
||||
'XLTCUSDT': [1],
|
||||
'LTC/ETH': [1]
|
||||
}
|
||||
|
||||
|
||||
def test__set_leverage_kraken(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id="kraken")
|
||||
exchange._set_leverage(1)
|
||||
assert 'leverage' not in exchange._params
|
||||
exchange._set_leverage(3)
|
||||
assert exchange._params['leverage'] == 3
|
||||
exchange._set_leverage(1.0)
|
||||
assert 'leverage' not in exchange._params
|
||||
exchange._set_leverage(3.0)
|
||||
assert exchange._params['leverage'] == 3
|
||||
|
@@ -22,9 +22,10 @@ twentyfive_hours = Decimal(25.0)
|
||||
('kraken', 0.00025, five_hours, 0.045),
|
||||
('kraken', 0.00025, twentyfive_hours, 0.12),
|
||||
# FTX
|
||||
# TODO-lev: - implement FTX tests
|
||||
# ('ftx', Decimal(0.0005), ten_mins, 0.06),
|
||||
# ('ftx', Decimal(0.0005), five_hours, 0.045),
|
||||
('ftx', 0.0005, ten_mins, 0.00125),
|
||||
('ftx', 0.00025, ten_mins, 0.000625),
|
||||
('ftx', 0.00025, five_hours, 0.003125),
|
||||
('ftx', 0.00025, twentyfive_hours, 0.015625),
|
||||
])
|
||||
def test_interest(exchange, interest_rate, hours, expected):
|
||||
borrowed = Decimal(60.0)
|
@@ -1,271 +0,0 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||||
|
||||
from functools import reduce
|
||||
from typing import Any, Callable, Dict, List
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
from skopt.space import Categorical, Dimension, Integer
|
||||
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
from freqtrade.optimize.hyperopt_interface import IHyperOpt
|
||||
|
||||
|
||||
class DefaultHyperOpt(IHyperOpt):
|
||||
"""
|
||||
Default hyperopt provided by the Freqtrade bot.
|
||||
You can override it with your own Hyperopt
|
||||
"""
|
||||
@staticmethod
|
||||
def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Add several indicators needed for buy and sell strategies defined below.
|
||||
"""
|
||||
# ADX
|
||||
dataframe['adx'] = ta.ADX(dataframe)
|
||||
# MACD
|
||||
macd = ta.MACD(dataframe)
|
||||
dataframe['macd'] = macd['macd']
|
||||
dataframe['macdsignal'] = macd['macdsignal']
|
||||
# MFI
|
||||
dataframe['mfi'] = ta.MFI(dataframe)
|
||||
# RSI
|
||||
dataframe['rsi'] = ta.RSI(dataframe)
|
||||
# Stochastic Fast
|
||||
stoch_fast = ta.STOCHF(dataframe)
|
||||
dataframe['fastd'] = stoch_fast['fastd']
|
||||
# Minus-DI
|
||||
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||
# Bollinger bands
|
||||
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||
dataframe['bb_lowerband'] = bollinger['lower']
|
||||
dataframe['bb_upperband'] = bollinger['upper']
|
||||
# SAR
|
||||
dataframe['sar'] = ta.SAR(dataframe)
|
||||
|
||||
return dataframe
|
||||
|
||||
@staticmethod
|
||||
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
|
||||
"""
|
||||
Define the buy strategy parameters to be used by Hyperopt.
|
||||
"""
|
||||
def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Buy strategy Hyperopt will build and use.
|
||||
"""
|
||||
long_conditions = []
|
||||
short_conditions = []
|
||||
|
||||
# GUARDS AND TRENDS
|
||||
if 'mfi-enabled' in params and params['mfi-enabled']:
|
||||
long_conditions.append(dataframe['mfi'] < params['mfi-value'])
|
||||
short_conditions.append(dataframe['mfi'] > params['short-mfi-value'])
|
||||
if 'fastd-enabled' in params and params['fastd-enabled']:
|
||||
long_conditions.append(dataframe['fastd'] < params['fastd-value'])
|
||||
short_conditions.append(dataframe['fastd'] > params['short-fastd-value'])
|
||||
if 'adx-enabled' in params and params['adx-enabled']:
|
||||
long_conditions.append(dataframe['adx'] > params['adx-value'])
|
||||
short_conditions.append(dataframe['adx'] < params['short-adx-value'])
|
||||
if 'rsi-enabled' in params and params['rsi-enabled']:
|
||||
long_conditions.append(dataframe['rsi'] < params['rsi-value'])
|
||||
short_conditions.append(dataframe['rsi'] > params['short-rsi-value'])
|
||||
|
||||
# TRIGGERS
|
||||
if 'trigger' in params:
|
||||
if params['trigger'] == 'boll':
|
||||
long_conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
|
||||
short_conditions.append(dataframe['close'] > dataframe['bb_upperband'])
|
||||
if params['trigger'] == 'macd_cross_signal':
|
||||
long_conditions.append(qtpylib.crossed_above(
|
||||
dataframe['macd'],
|
||||
dataframe['macdsignal']
|
||||
))
|
||||
short_conditions.append(qtpylib.crossed_below(
|
||||
dataframe['macd'],
|
||||
dataframe['macdsignal']
|
||||
))
|
||||
if params['trigger'] == 'sar_reversal':
|
||||
long_conditions.append(qtpylib.crossed_above(
|
||||
dataframe['close'],
|
||||
dataframe['sar']
|
||||
))
|
||||
short_conditions.append(qtpylib.crossed_below(
|
||||
dataframe['close'],
|
||||
dataframe['sar']
|
||||
))
|
||||
|
||||
if long_conditions:
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, long_conditions),
|
||||
'buy'] = 1
|
||||
|
||||
if short_conditions:
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, short_conditions),
|
||||
'enter_short'] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
return populate_buy_trend
|
||||
|
||||
@staticmethod
|
||||
def indicator_space() -> List[Dimension]:
|
||||
"""
|
||||
Define your Hyperopt space for searching buy strategy parameters.
|
||||
"""
|
||||
return [
|
||||
Integer(10, 25, name='mfi-value'),
|
||||
Integer(15, 45, name='fastd-value'),
|
||||
Integer(20, 50, name='adx-value'),
|
||||
Integer(20, 40, name='rsi-value'),
|
||||
Integer(75, 90, name='short-mfi-value'),
|
||||
Integer(55, 85, name='short-fastd-value'),
|
||||
Integer(50, 80, name='short-adx-value'),
|
||||
Integer(60, 80, name='short-rsi-value'),
|
||||
Categorical([True, False], name='mfi-enabled'),
|
||||
Categorical([True, False], name='fastd-enabled'),
|
||||
Categorical([True, False], name='adx-enabled'),
|
||||
Categorical([True, False], name='rsi-enabled'),
|
||||
Categorical(['boll', 'macd_cross_signal', 'sar_reversal'], name='trigger')
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
|
||||
"""
|
||||
Define the sell strategy parameters to be used by Hyperopt.
|
||||
"""
|
||||
def populate_sell_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Sell strategy Hyperopt will build and use.
|
||||
"""
|
||||
exit_long_conditions = []
|
||||
exit_short_conditions = []
|
||||
|
||||
# GUARDS AND TRENDS
|
||||
if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']:
|
||||
exit_long_conditions.append(dataframe['mfi'] > params['sell-mfi-value'])
|
||||
exit_short_conditions.append(dataframe['mfi'] < params['exit-short-mfi-value'])
|
||||
if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']:
|
||||
exit_long_conditions.append(dataframe['fastd'] > params['sell-fastd-value'])
|
||||
exit_short_conditions.append(dataframe['fastd'] < params['exit-short-fastd-value'])
|
||||
if 'sell-adx-enabled' in params and params['sell-adx-enabled']:
|
||||
exit_long_conditions.append(dataframe['adx'] < params['sell-adx-value'])
|
||||
exit_short_conditions.append(dataframe['adx'] > params['exit-short-adx-value'])
|
||||
if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']:
|
||||
exit_long_conditions.append(dataframe['rsi'] > params['sell-rsi-value'])
|
||||
exit_short_conditions.append(dataframe['rsi'] < params['exit-short-rsi-value'])
|
||||
|
||||
# TRIGGERS
|
||||
if 'sell-trigger' in params:
|
||||
if params['sell-trigger'] == 'sell-boll':
|
||||
exit_long_conditions.append(dataframe['close'] > dataframe['bb_upperband'])
|
||||
exit_short_conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
|
||||
if params['sell-trigger'] == 'sell-macd_cross_signal':
|
||||
exit_long_conditions.append(qtpylib.crossed_above(
|
||||
dataframe['macdsignal'],
|
||||
dataframe['macd']
|
||||
))
|
||||
exit_short_conditions.append(qtpylib.crossed_below(
|
||||
dataframe['macdsignal'],
|
||||
dataframe['macd']
|
||||
))
|
||||
if params['sell-trigger'] == 'sell-sar_reversal':
|
||||
exit_long_conditions.append(qtpylib.crossed_above(
|
||||
dataframe['sar'],
|
||||
dataframe['close']
|
||||
))
|
||||
exit_short_conditions.append(qtpylib.crossed_below(
|
||||
dataframe['sar'],
|
||||
dataframe['close']
|
||||
))
|
||||
|
||||
if exit_long_conditions:
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, exit_long_conditions),
|
||||
'sell'] = 1
|
||||
|
||||
if exit_short_conditions:
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, exit_short_conditions),
|
||||
'exit-short'] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
return populate_sell_trend
|
||||
|
||||
@staticmethod
|
||||
def sell_indicator_space() -> List[Dimension]:
|
||||
"""
|
||||
Define your Hyperopt space for searching sell strategy parameters.
|
||||
"""
|
||||
return [
|
||||
Integer(75, 100, name='sell-mfi-value'),
|
||||
Integer(50, 100, name='sell-fastd-value'),
|
||||
Integer(50, 100, name='sell-adx-value'),
|
||||
Integer(60, 100, name='sell-rsi-value'),
|
||||
Integer(1, 25, name='exit-short-mfi-value'),
|
||||
Integer(1, 50, name='exit-short-fastd-value'),
|
||||
Integer(1, 50, name='exit-short-adx-value'),
|
||||
Integer(1, 40, name='exit-short-rsi-value'),
|
||||
Categorical([True, False], name='sell-mfi-enabled'),
|
||||
Categorical([True, False], name='sell-fastd-enabled'),
|
||||
Categorical([True, False], name='sell-adx-enabled'),
|
||||
Categorical([True, False], name='sell-rsi-enabled'),
|
||||
Categorical(['sell-boll',
|
||||
'sell-macd_cross_signal',
|
||||
'sell-sar_reversal'],
|
||||
name='sell-trigger')
|
||||
]
|
||||
|
||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Based on TA indicators. Should be a copy of same method from strategy.
|
||||
Must align to populate_indicators in this file.
|
||||
Only used when --spaces does not include buy space.
|
||||
"""
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['close'] < dataframe['bb_lowerband']) &
|
||||
(dataframe['mfi'] < 16) &
|
||||
(dataframe['adx'] > 25) &
|
||||
(dataframe['rsi'] < 21)
|
||||
),
|
||||
'buy'] = 1
|
||||
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['close'] > dataframe['bb_upperband']) &
|
||||
(dataframe['mfi'] < 84) &
|
||||
(dataframe['adx'] > 75) &
|
||||
(dataframe['rsi'] < 79)
|
||||
),
|
||||
'enter_short'] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Based on TA indicators. Should be a copy of same method from strategy.
|
||||
Must align to populate_indicators in this file.
|
||||
Only used when --spaces does not include sell space.
|
||||
"""
|
||||
dataframe.loc[
|
||||
(
|
||||
(qtpylib.crossed_above(
|
||||
dataframe['macdsignal'], dataframe['macd']
|
||||
)) &
|
||||
(dataframe['fastd'] > 54)
|
||||
),
|
||||
'sell'] = 1
|
||||
|
||||
dataframe.loc[
|
||||
(
|
||||
(qtpylib.crossed_below(
|
||||
dataframe['macdsignal'], dataframe['macd']
|
||||
)) &
|
||||
(dataframe['fastd'] < 46)
|
||||
),
|
||||
'exit_short'] = 1
|
||||
|
||||
return dataframe
|
@@ -887,6 +887,10 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.value != 35
|
||||
assert hyperopt.backtesting.strategy.sell_rsi.value != 74
|
||||
|
||||
hyperopt.custom_hyperopt.generate_estimator = lambda *args, **kwargs: 'ET1'
|
||||
with pytest.raises(OperationalException, match="Estimator ET1 not supported."):
|
||||
hyperopt.get_optimizer([], 2)
|
||||
|
||||
|
||||
def test_SKDecimal():
|
||||
space = SKDecimal(1, 2, decimals=2)
|
||||
|
@@ -4,6 +4,7 @@ import time
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import pytest
|
||||
import time_machine
|
||||
|
||||
from freqtrade.constants import AVAILABLE_PAIRLISTS
|
||||
from freqtrade.exceptions import OperationalException
|
||||
@@ -11,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")
|
||||
@@ -662,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}]
|
||||
|
||||
@@ -815,32 +842,63 @@ def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tick
|
||||
|
||||
|
||||
def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, ohlcv_history):
|
||||
ohlcv_data = {
|
||||
('ETH/BTC', '1d'): ohlcv_history,
|
||||
('TKN/BTC', '1d'): ohlcv_history,
|
||||
('LTC/BTC', '1d'): ohlcv_history,
|
||||
}
|
||||
mocker.patch.multiple('freqtrade.exchange.Exchange',
|
||||
markets=PropertyMock(return_value=markets),
|
||||
exchange_has=MagicMock(return_value=True),
|
||||
get_tickers=tickers
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data),
|
||||
)
|
||||
with time_machine.travel("2021-09-01 05:00:00 +00:00") as t:
|
||||
ohlcv_data = {
|
||||
('ETH/BTC', '1d'): ohlcv_history,
|
||||
('TKN/BTC', '1d'): ohlcv_history,
|
||||
('LTC/BTC', '1d'): ohlcv_history,
|
||||
}
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
markets=PropertyMock(return_value=markets),
|
||||
exchange_has=MagicMock(return_value=True),
|
||||
get_tickers=tickers,
|
||||
refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data),
|
||||
)
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf_agefilter)
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 0
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert len(freqtrade.pairlists.whitelist) == 3
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count > 0
|
||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf_agefilter)
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 0
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert len(freqtrade.pairlists.whitelist) == 3
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count > 0
|
||||
|
||||
previous_call_count = freqtrade.exchange.refresh_latest_ohlcv.call_count
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert len(freqtrade.pairlists.whitelist) == 3
|
||||
# Called once for XRP/BTC
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == previous_call_count + 1
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert len(freqtrade.pairlists.whitelist) == 3
|
||||
# Call to XRP/BTC cached
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 2
|
||||
|
||||
ohlcv_data = {
|
||||
('ETH/BTC', '1d'): ohlcv_history,
|
||||
('TKN/BTC', '1d'): ohlcv_history,
|
||||
('LTC/BTC', '1d'): ohlcv_history,
|
||||
('XRP/BTC', '1d'): ohlcv_history.iloc[[0]],
|
||||
}
|
||||
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data)
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert len(freqtrade.pairlists.whitelist) == 3
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 1
|
||||
|
||||
# Move to next day
|
||||
t.move_to("2021-09-02 01:00:00 +00:00")
|
||||
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data)
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert len(freqtrade.pairlists.whitelist) == 3
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 1
|
||||
|
||||
# Move another day with fresh mocks (now the pair is old enough)
|
||||
t.move_to("2021-09-03 01:00:00 +00:00")
|
||||
# Called once for XRP/BTC
|
||||
ohlcv_data = {
|
||||
('ETH/BTC', '1d'): ohlcv_history,
|
||||
('TKN/BTC', '1d'): ohlcv_history,
|
||||
('LTC/BTC', '1d'): ohlcv_history,
|
||||
('XRP/BTC', '1d'): ohlcv_history,
|
||||
}
|
||||
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data)
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert len(freqtrade.pairlists.whitelist) == 4
|
||||
# Called once (only for XRP/BTC)
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 1
|
||||
|
||||
|
||||
def test_OffsetFilter_error(mocker, whitelist_conf) -> None:
|
||||
|
@@ -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'
|
||||
]}
|
||||
|
@@ -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)
|
||||
|
75
tests/strategy/strats/informative_decorator_strategy.py
Normal file
75
tests/strategy/strats/informative_decorator_strategy.py
Normal 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
|
@@ -648,7 +648,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', [
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
||||
|
@@ -98,11 +98,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',
|
||||
@@ -112,45 +116,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',
|
||||
@@ -239,8 +212,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)
|
||||
@@ -254,9 +233,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,
|
||||
)
|
||||
@@ -273,46 +252,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('Exit for NEO/BTC detected. 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('Exit for NEO/BTC detected. Reason: stop_loss', caplog)
|
||||
assert trade.sell_reason == SellType.STOP_LOSS.value
|
||||
|
||||
|
||||
def test_total_open_trades_stakes(mocker, default_conf, ticker, fee) -> None:
|
||||
@@ -406,37 +349,16 @@ def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order,
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order_open,
|
||||
limit_sell_order_open, fee, mocker, is_short) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
enter_mock = (
|
||||
MagicMock(return_value=limit_sell_order_open)
|
||||
if is_short else
|
||||
MagicMock(return_value=limit_buy_order_open)
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
create_order=enter_mock,
|
||||
get_fee=fee,
|
||||
)
|
||||
default_conf['stake_amount'] = 0.0005
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
freqtrade.create_trade('ETH/BTC')
|
||||
rate, amount = enter_mock.call_args[1]['rate'], enter_mock.call_args[1]['amount']
|
||||
assert rate * amount <= default_conf['stake_amount']
|
||||
|
||||
# TODO-lev: paramatrize and convert to USDT
|
||||
# @pytest.mark.parametrize("stake_amount,leverage", [
|
||||
# "buy, sell"
|
||||
# ])
|
||||
|
||||
|
||||
def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_order_open,
|
||||
fee, mocker, caplog) -> 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, is_short
|
||||
) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
buy_mock = MagicMock(return_value=limit_buy_order_open)
|
||||
@@ -446,58 +368,33 @@ def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_ord
|
||||
create_order=buy_mock,
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
default_conf['max_open_trades'] = max_open_trades
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade.config['stake_amount'] = 0.000000005
|
||||
|
||||
freqtrade.config['stake_amount'] = stake_amount
|
||||
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(
|
||||
@@ -506,36 +403,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")
|
||||
@@ -953,7 +834,7 @@ def test_execute_entry(mocker, default_conf, fee, limit_buy_order, limit_sell_or
|
||||
# Fail to get price...
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0))
|
||||
|
||||
with pytest.raises(PricingError, match=f"Could not determine {'sell' if is_short else 'buy'} price."):
|
||||
with pytest.raises(PricingError, match=f"Could not determine {enter_side(is_short)} price."):
|
||||
freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
|
||||
|
||||
# In case of custom entry price
|
||||
@@ -1338,6 +1219,7 @@ def test_create_stoploss_order_insufficient_funds(
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, is_short,
|
||||
limit_buy_order, limit_sell_order) -> None:
|
||||
# TODO-lev: test for short
|
||||
# When trailing stoploss is set
|
||||
enter_order = limit_sell_order if is_short else limit_buy_order
|
||||
exit_order = limit_buy_order if is_short else limit_sell_order
|
||||
@@ -1437,7 +1319,8 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, is_shor
|
||||
pair='ETH/BTC',
|
||||
order_types=freqtrade.strategy.order_types,
|
||||
stop_price=0.00002346 * 0.95,
|
||||
side="sell"
|
||||
side="sell",
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
# price fell below stoploss, so dry-run sells trade.
|
||||
@@ -1540,6 +1423,7 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee, is_s
|
||||
exit_order = limit_buy_order if is_short else limit_sell_order
|
||||
|
||||
# When trailing stoploss is set
|
||||
# TODO-lev: test for short
|
||||
stoploss = MagicMock(return_value={'id': 13434334})
|
||||
patch_RPCManager(mocker)
|
||||
mocker.patch.multiple(
|
||||
@@ -1635,7 +1519,8 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee, is_s
|
||||
pair='ETH/BTC',
|
||||
order_types=freqtrade.strategy.order_types,
|
||||
stop_price=0.00002346 * 0.96,
|
||||
side="sell"
|
||||
side="sell",
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
# price fell below stoploss, so dry-run sells trade.
|
||||
@@ -1764,34 +1649,32 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, is
|
||||
pair='NEO/BTC',
|
||||
order_types=freqtrade.strategy.order_types,
|
||||
stop_price=0.00002346 * 0.99,
|
||||
side="sell"
|
||||
side="sell",
|
||||
leverage=1.0
|
||||
)
|
||||
|
||||
|
||||
def test_enter_positions(mocker, default_conf, caplog) -> None:
|
||||
@pytest.mark.parametrize('return_value,side_effect,log_message', [
|
||||
(False, None, 'Found no enter 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)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@@ -1896,9 +1779,15 @@ def test_update_trade_state(mocker, default_conf, limit_buy_order,
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@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,
|
||||
limit_sell_order, is_short, mocker):
|
||||
mocker, initial_amount, has_rounding_fee,
|
||||
limit_sell_order, is_short, caplog):
|
||||
order = limit_sell_order if is_short else limit_buy_order
|
||||
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))
|
||||
@@ -1919,38 +1808,9 @@ def test_update_trade_state_withorderdict(default_conf, trades_for_order, limit_
|
||||
)
|
||||
freqtrade.update_trade_state(trade, '123456', order)
|
||||
assert trade.amount != amount
|
||||
assert trade.amount == order['amount']
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_update_trade_state_withorderdict_rounding_fee(
|
||||
default_conf, trades_for_order, fee, limit_buy_order, limit_sell_order,
|
||||
mocker, caplog, is_short
|
||||
):
|
||||
order = limit_sell_order if is_short else limit_buy_order
|
||||
trades_for_order[0]['amount'] = 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,
|
||||
is_short=is_short
|
||||
)
|
||||
freqtrade.update_trade_state(trade, '123456', order)
|
||||
assert trade.amount != amount
|
||||
assert trade.amount == order['amount']
|
||||
assert log_has_re(r'Applying fee on amount for .*', caplog)
|
||||
assert trade.amount == limit_buy_order['amount']
|
||||
if has_rounding_fee:
|
||||
assert log_has_re(r'Applying fee on amount for .*', caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@@ -3354,16 +3214,28 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf, ticker, fee,
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, limit_buy_order_open,
|
||||
is_short, 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, is_short,
|
||||
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,
|
||||
@@ -3373,131 +3245,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, enter_long=False, exit_long=True)
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, limit_buy_order_open,
|
||||
is_short, 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, enter_long=False, exit_long=True)
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.sell_reason == SellType.SELL_SIGNAL.value
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, limit_buy_order_open,
|
||||
is_short, 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, enter_long=False, exit_long=True)
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, limit_buy_order_open,
|
||||
is_short, 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, enter_long=False, exit_long=True)
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.sell_reason == SellType.SELL_SIGNAL.value
|
||||
assert trade.sell_reason == sell_type
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@@ -3536,11 +3306,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(
|
||||
@@ -3554,37 +3328,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 exit trade."):
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_short", [False, True])
|
||||
@@ -3847,8 +3603,8 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, limit_buy_orde
|
||||
def test_tsl_only_offset_reached(default_conf, limit_buy_order_usdt, limit_buy_order_usdt_open,
|
||||
fee, is_short, limit_sell_order_usdt,
|
||||
limit_sell_order_usdt_open, caplog, mocker) -> None:
|
||||
limit_order = limit_sell_usdt_order if is_short else limit_buy_order_usdt
|
||||
limit_order_open = limit_sell_order_usdt_open if is_short else limit_buy_order_open_usdt
|
||||
limit_order = limit_sell_order_usdt if is_short else limit_buy_order_usdt
|
||||
limit_order_open = limit_sell_order_usdt_open if is_short else limit_buy_order_usdt_open
|
||||
enter_price = limit_order['price']
|
||||
# enter_price: 2.0
|
||||
|
||||
@@ -3885,9 +3641,9 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order_usdt, limit_buy_o
|
||||
# Raise ticker above buy price
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
MagicMock(return_value={
|
||||
'bid': buy_price + 0.0000004,
|
||||
'ask': buy_price + 0.0000004,
|
||||
'last': buy_price + 0.0000004
|
||||
'bid': enter_price + 0.0000004,
|
||||
'ask': enter_price + 0.0000004,
|
||||
'last': enter_price + 0.0000004
|
||||
}))
|
||||
|
||||
# stop-loss should not be adjusted as offset is not reached yet
|
||||
@@ -3900,9 +3656,9 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order_usdt, limit_buy_o
|
||||
# price rises above the offset (rises 12% when the offset is 5.5%)
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
MagicMock(return_value={
|
||||
'bid': buy_price + 0.0000014,
|
||||
'ask': buy_price + 0.0000014,
|
||||
'last': buy_price + 0.0000014
|
||||
'bid': enter_price + 0.0000014,
|
||||
'ask': enter_price + 0.0000014,
|
||||
'last': enter_price + 0.0000014
|
||||
}))
|
||||
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
@@ -4384,50 +4140,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(default_conf, mocker, order_book_l2) -> None:
|
||||
|
32
tests/test_periodiccache.py
Normal file
32
tests/test_periodiccache.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import time_machine
|
||||
|
||||
from freqtrade.configuration import PeriodicCache
|
||||
|
||||
|
||||
def test_ttl_cache():
|
||||
|
||||
with time_machine.travel("2021-09-01 05:00:00 +00:00") as t:
|
||||
|
||||
cache = PeriodicCache(5, ttl=60)
|
||||
cache1h = PeriodicCache(5, ttl=3600)
|
||||
|
||||
assert cache.timer() == 1630472400.0
|
||||
cache['a'] = 1235
|
||||
cache1h['a'] = 555123
|
||||
assert 'a' in cache
|
||||
assert 'a' in cache1h
|
||||
|
||||
t.move_to("2021-09-01 05:00:59 +00:00")
|
||||
assert 'a' in cache
|
||||
assert 'a' in cache1h
|
||||
|
||||
# Cache expired
|
||||
t.move_to("2021-09-01 05:01:00 +00:00")
|
||||
assert 'a' not in cache
|
||||
assert 'a' in cache1h
|
||||
|
||||
t.move_to("2021-09-01 05:59:59 +00:00")
|
||||
assert 'a' in cache1h
|
||||
|
||||
t.move_to("2021-09-01 06:00:00 +00:00")
|
||||
assert 'a' not in cache1h
|
Reference in New Issue
Block a user