2023-01-01 11:37:15 +00:00
|
|
|
# pragma pylint: disable=missing-docstring, protected-access, invalid-name
|
|
|
|
|
2023-03-12 15:45:56 +00:00
|
|
|
import re
|
|
|
|
import shutil
|
2023-03-10 07:59:07 +00:00
|
|
|
import sys
|
2023-03-12 15:45:56 +00:00
|
|
|
from pathlib import Path
|
2023-03-10 07:59:07 +00:00
|
|
|
|
2023-03-10 08:23:56 +00:00
|
|
|
import pytest
|
|
|
|
|
2023-03-12 15:45:56 +00:00
|
|
|
from freqtrade.commands.strategy_utils_commands import start_strategy_update
|
2023-01-01 11:37:15 +00:00
|
|
|
from freqtrade.strategy.strategyupdater import StrategyUpdater
|
2023-03-12 15:45:56 +00:00
|
|
|
from tests.conftest import get_args
|
2023-01-01 11:37:15 +00:00
|
|
|
|
|
|
|
|
2023-03-12 15:26:12 +00:00
|
|
|
if sys.version_info < (3, 9):
|
|
|
|
pytest.skip("StrategyUpdater is not compatible with Python 3.8", allow_module_level=True)
|
|
|
|
|
|
|
|
|
2023-03-12 15:45:56 +00:00
|
|
|
def test_strategy_updater_start(tmpdir, capsys) -> None:
|
|
|
|
# Effective test without mocks.
|
|
|
|
teststrats = Path(__file__).parent / 'strategy/strats'
|
|
|
|
tmpdirp = Path(tmpdir) / "strategies"
|
|
|
|
tmpdirp.mkdir()
|
|
|
|
shutil.copy(teststrats / 'strategy_test_v2.py', tmpdirp)
|
|
|
|
old_code = (teststrats / 'strategy_test_v2.py').read_text()
|
|
|
|
|
|
|
|
args = [
|
|
|
|
"strategy-updater",
|
|
|
|
"--userdir",
|
|
|
|
str(tmpdir),
|
|
|
|
"--strategy-list",
|
|
|
|
"StrategyTestV2"
|
|
|
|
]
|
|
|
|
pargs = get_args(args)
|
|
|
|
pargs['config'] = None
|
|
|
|
|
|
|
|
start_strategy_update(pargs)
|
|
|
|
|
|
|
|
assert Path(tmpdir / "strategies_orig_updater").exists()
|
|
|
|
# Backup file exists
|
|
|
|
assert Path(tmpdir / "strategies_orig_updater" / 'strategy_test_v2.py').exists()
|
|
|
|
# updated file exists
|
|
|
|
new_file = Path(tmpdirp / 'strategy_test_v2.py')
|
|
|
|
assert new_file.exists()
|
|
|
|
new_code = new_file.read_text()
|
|
|
|
assert 'INTERFACE_VERSION = 3' in new_code
|
|
|
|
assert 'INTERFACE_VERSION = 2' in old_code
|
|
|
|
captured = capsys.readouterr()
|
|
|
|
|
|
|
|
assert 'Conversion of strategy_test_v2.py started.' in captured.out
|
|
|
|
assert re.search(r'Conversion of strategy_test_v2\.py took .* seconds', captured.out)
|
|
|
|
|
|
|
|
|
2023-03-12 15:26:12 +00:00
|
|
|
def test_strategy_updater_methods(default_conf, caplog) -> None:
|
2023-03-10 08:23:56 +00:00
|
|
|
|
|
|
|
instance_strategy_updater = StrategyUpdater()
|
|
|
|
modified_code1 = instance_strategy_updater.update_code("""
|
|
|
|
class testClass(IStrategy):
|
|
|
|
def populate_buy_trend():
|
|
|
|
pass
|
|
|
|
def populate_sell_trend():
|
|
|
|
pass
|
|
|
|
def check_buy_timeout():
|
|
|
|
pass
|
|
|
|
def check_sell_timeout():
|
|
|
|
pass
|
|
|
|
def custom_sell():
|
|
|
|
pass
|
|
|
|
""")
|
2023-03-12 15:26:12 +00:00
|
|
|
|
|
|
|
assert "populate_entry_trend" in modified_code1
|
|
|
|
assert "populate_exit_trend" in modified_code1
|
|
|
|
assert "check_entry_timeout" in modified_code1
|
|
|
|
assert "check_exit_timeout" in modified_code1
|
|
|
|
assert "custom_exit" in modified_code1
|
|
|
|
assert "INTERFACE_VERSION = 3" in modified_code1
|
|
|
|
|
|
|
|
|
|
|
|
def test_strategy_updater_params(default_conf, caplog) -> None:
|
|
|
|
instance_strategy_updater = StrategyUpdater()
|
|
|
|
|
2023-03-10 08:23:56 +00:00
|
|
|
modified_code2 = instance_strategy_updater.update_code("""
|
|
|
|
ticker_interval = '15m'
|
|
|
|
buy_some_parameter = IntParameter(space='buy')
|
|
|
|
sell_some_parameter = IntParameter(space='sell')
|
|
|
|
""")
|
2023-03-12 15:26:12 +00:00
|
|
|
|
|
|
|
assert "timeframe" in modified_code2
|
|
|
|
# check for not editing hyperopt spaces
|
|
|
|
assert "space='buy'" in modified_code2
|
|
|
|
assert "space='sell'" in modified_code2
|
|
|
|
|
|
|
|
|
|
|
|
def test_strategy_updater_constants(default_conf, caplog) -> None:
|
|
|
|
instance_strategy_updater = StrategyUpdater()
|
2023-03-10 08:23:56 +00:00
|
|
|
modified_code3 = instance_strategy_updater.update_code("""
|
|
|
|
use_sell_signal = True
|
|
|
|
sell_profit_only = True
|
|
|
|
sell_profit_offset = True
|
|
|
|
ignore_roi_if_buy_signal = True
|
|
|
|
forcebuy_enable = True
|
|
|
|
""")
|
2023-03-12 15:26:12 +00:00
|
|
|
|
|
|
|
assert "use_exit_signal" in modified_code3
|
|
|
|
assert "exit_profit_only" in modified_code3
|
|
|
|
assert "exit_profit_offset" in modified_code3
|
|
|
|
assert "ignore_roi_if_entry_signal" in modified_code3
|
|
|
|
assert "force_entry_enable" in modified_code3
|
|
|
|
|
|
|
|
|
|
|
|
def test_strategy_updater_df_columns(default_conf, caplog) -> None:
|
|
|
|
instance_strategy_updater = StrategyUpdater()
|
|
|
|
modified_code = instance_strategy_updater.update_code("""
|
2023-03-10 08:23:56 +00:00
|
|
|
dataframe.loc[reduce(lambda x, y: x & y, conditions), ["buy", "buy_tag"]] = (1, "buy_signal_1")
|
|
|
|
dataframe.loc[reduce(lambda x, y: x & y, conditions), 'sell'] = 1
|
|
|
|
""")
|
2023-03-12 15:26:12 +00:00
|
|
|
|
|
|
|
assert "enter_long" in modified_code
|
|
|
|
assert "exit_long" in modified_code
|
|
|
|
assert "enter_tag" in modified_code
|
|
|
|
|
|
|
|
|
|
|
|
def test_strategy_updater_method_params(default_conf, caplog) -> None:
|
|
|
|
instance_strategy_updater = StrategyUpdater()
|
|
|
|
modified_code = instance_strategy_updater.update_code("""
|
2023-03-10 08:23:56 +00:00
|
|
|
def confirm_trade_exit(sell_reason: str):
|
2023-03-19 10:16:54 +00:00
|
|
|
nr_orders = trade.nr_of_successful_buys
|
2023-03-10 08:23:56 +00:00
|
|
|
pass
|
2023-03-10 07:59:07 +00:00
|
|
|
""")
|
2023-03-12 15:26:12 +00:00
|
|
|
assert "exit_reason" in modified_code
|
2023-03-19 10:16:54 +00:00
|
|
|
assert "nr_orders = trade.nr_of_successful_entries" in modified_code
|
2023-03-12 15:26:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_strategy_updater_dicts(default_conf, caplog) -> None:
|
|
|
|
instance_strategy_updater = StrategyUpdater()
|
|
|
|
modified_code = instance_strategy_updater.update_code("""
|
2023-03-10 08:23:56 +00:00
|
|
|
order_time_in_force = {
|
|
|
|
'buy': 'gtc',
|
|
|
|
'sell': 'ioc'
|
|
|
|
}
|
|
|
|
order_types = {
|
|
|
|
'buy': 'limit',
|
|
|
|
'sell': 'market',
|
|
|
|
'stoploss': 'market',
|
|
|
|
'stoploss_on_exchange': False
|
|
|
|
}
|
|
|
|
unfilledtimeout = {
|
|
|
|
'buy': 1,
|
|
|
|
'sell': 2
|
|
|
|
}
|
|
|
|
""")
|
|
|
|
|
2023-03-12 15:26:12 +00:00
|
|
|
assert "'entry': 'gtc'" in modified_code
|
|
|
|
assert "'exit': 'ioc'" in modified_code
|
|
|
|
assert "'entry': 'limit'" in modified_code
|
|
|
|
assert "'exit': 'market'" in modified_code
|
|
|
|
assert "'entry': 1" in modified_code
|
|
|
|
assert "'exit': 2" in modified_code
|
|
|
|
|
|
|
|
|
|
|
|
def test_strategy_updater_comparisons(default_conf, caplog) -> None:
|
|
|
|
instance_strategy_updater = StrategyUpdater()
|
|
|
|
modified_code = instance_strategy_updater.update_code("""
|
2023-03-10 08:23:56 +00:00
|
|
|
def confirm_trade_exit(sell_reason):
|
|
|
|
if (sell_reason == 'stop_loss'):
|
2023-01-01 21:03:45 +00:00
|
|
|
pass
|
2023-03-10 08:23:56 +00:00
|
|
|
""")
|
2023-03-12 15:26:12 +00:00
|
|
|
assert "exit_reason" in modified_code
|
|
|
|
assert "exit_reason == 'stop_loss'" in modified_code
|
|
|
|
|
|
|
|
|
|
|
|
def test_strategy_updater_strings(default_conf, caplog) -> None:
|
|
|
|
instance_strategy_updater = StrategyUpdater()
|
|
|
|
|
|
|
|
modified_code = instance_strategy_updater.update_code("""
|
2023-03-10 08:23:56 +00:00
|
|
|
sell_reason == 'sell_signal'
|
|
|
|
sell_reason == 'force_sell'
|
|
|
|
sell_reason == 'emergency_sell'
|
|
|
|
""")
|
2023-03-12 15:26:12 +00:00
|
|
|
|
|
|
|
# those tests currently don't work, next in line.
|
|
|
|
assert "exit_signal" in modified_code
|
|
|
|
assert "exit_reason" in modified_code
|
|
|
|
assert "force_exit" in modified_code
|
|
|
|
assert "emergency_exit" in modified_code
|
|
|
|
|
|
|
|
|
|
|
|
def test_strategy_updater_comments(default_conf, caplog) -> None:
|
|
|
|
instance_strategy_updater = StrategyUpdater()
|
|
|
|
modified_code = instance_strategy_updater.update_code("""
|
2023-03-10 08:23:56 +00:00
|
|
|
# This is the 1st comment
|
|
|
|
import talib.abstract as ta
|
|
|
|
# This is the 2nd comment
|
|
|
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|
|
|
|
|
|
|
|
|
|
|
class someStrategy(IStrategy):
|
2023-03-12 15:45:56 +00:00
|
|
|
INTERFACE_VERSION = 2
|
2023-03-10 08:23:56 +00:00
|
|
|
# This is the 3rd comment
|
|
|
|
# This attribute will be overridden if the config file contains "minimal_roi"
|
|
|
|
minimal_roi = {
|
|
|
|
"0": 0.50
|
2023-03-10 07:59:07 +00:00
|
|
|
}
|
2023-02-17 20:01:09 +00:00
|
|
|
|
2023-03-10 08:23:56 +00:00
|
|
|
# This is the 4th comment
|
|
|
|
stoploss = -0.1
|
|
|
|
""")
|
2023-03-12 15:26:12 +00:00
|
|
|
|
|
|
|
assert "This is the 1st comment" in modified_code
|
|
|
|
assert "This is the 2nd comment" in modified_code
|
|
|
|
assert "This is the 3rd comment" in modified_code
|
2023-03-12 15:45:56 +00:00
|
|
|
assert "INTERFACE_VERSION = 3" in modified_code
|
2023-03-10 08:23:56 +00:00
|
|
|
# currently still missing:
|
|
|
|
# Webhook terminology, Telegram notification settings, Strategy/Config settings
|