Adding tests
This commit is contained in:
parent
82218d01f4
commit
a51e44eea3
@ -25,11 +25,15 @@ def start_strategy_update(args: Dict[str, Any]) -> None:
|
|||||||
config, enum_failed=True, recursive=config.get('recursive_strategy_search', False))
|
config, enum_failed=True, recursive=config.get('recursive_strategy_search', False))
|
||||||
|
|
||||||
filtered_strategy_objs = []
|
filtered_strategy_objs = []
|
||||||
for strategy_obj in strategy_objs:
|
|
||||||
for args_strategy in args['strategy_list']:
|
for args_strategy in args['strategy_list']:
|
||||||
|
found = False
|
||||||
|
for strategy_obj in strategy_objs:
|
||||||
if strategy_obj['name'] == args_strategy and strategy_obj not in filtered_strategy_objs:
|
if strategy_obj['name'] == args_strategy and strategy_obj not in filtered_strategy_objs:
|
||||||
filtered_strategy_objs.append(strategy_obj)
|
filtered_strategy_objs.append(strategy_obj)
|
||||||
|
found = True
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if not found:
|
||||||
print(f"strategy {strategy_obj['name']} could not be loaded or found and is skipped.")
|
print(f"strategy {strategy_obj['name']} could not be loaded or found and is skipped.")
|
||||||
|
|
||||||
for filtered_strategy_obj in filtered_strategy_objs:
|
for filtered_strategy_obj in filtered_strategy_objs:
|
||||||
|
@ -70,7 +70,6 @@ class StrategyUpdater:
|
|||||||
|
|
||||||
# update the code
|
# update the code
|
||||||
new_code = StrategyUpdater.update_code(self, old_code)
|
new_code = StrategyUpdater.update_code(self, old_code)
|
||||||
|
|
||||||
# write the modified code to the destination folder
|
# write the modified code to the destination folder
|
||||||
with open(source_file, 'w') as f:
|
with open(source_file, 'w') as f:
|
||||||
f.write(new_code)
|
f.write(new_code)
|
||||||
@ -82,8 +81,7 @@ class StrategyUpdater:
|
|||||||
tree = ast.parse(code)
|
tree = ast.parse(code)
|
||||||
|
|
||||||
# use the AST to update the code
|
# use the AST to update the code
|
||||||
updated_code = self.modify_ast(
|
updated_code = self.modify_ast(self, tree)
|
||||||
tree)
|
|
||||||
|
|
||||||
# return the modified code without executing it
|
# return the modified code without executing it
|
||||||
return updated_code
|
return updated_code
|
||||||
@ -107,18 +105,8 @@ class NameUpdater(ast.NodeTransformer):
|
|||||||
# traverse the AST recursively by calling the visitor method for each child node
|
# traverse the AST recursively by calling the visitor method for each child node
|
||||||
if hasattr(node, "_fields"):
|
if hasattr(node, "_fields"):
|
||||||
for field_name, field_value in ast.iter_fields(node):
|
for field_name, field_value in ast.iter_fields(node):
|
||||||
if not isinstance(field_value, ast.AST):
|
|
||||||
continue # to avoid unnecessary loops
|
|
||||||
self.visit(field_value)
|
|
||||||
self.generic_visit(field_value)
|
|
||||||
self.check_fields(field_value)
|
|
||||||
self.check_strategy_and_config_settings(node, field_value)
|
self.check_strategy_and_config_settings(node, field_value)
|
||||||
# add this check to handle the case where field_value is a slice
|
self.check_fields(field_value)
|
||||||
if isinstance(field_value, ast.Slice):
|
|
||||||
self.visit(field_value)
|
|
||||||
# add this check to handle the case where field_value is a target
|
|
||||||
if isinstance(field_value, ast.expr_context):
|
|
||||||
self.visit(field_value)
|
|
||||||
|
|
||||||
def check_fields(self, field_value):
|
def check_fields(self, field_value):
|
||||||
if isinstance(field_value, list):
|
if isinstance(field_value, list):
|
||||||
@ -139,10 +127,6 @@ class NameUpdater(ast.NodeTransformer):
|
|||||||
target.id == "unfilledtimeout"):
|
target.id == "unfilledtimeout"):
|
||||||
for key in field_value.keys:
|
for key in field_value.keys:
|
||||||
self.visit(key)
|
self.visit(key)
|
||||||
# Check if the target is a Subscript object with a "value" attribute
|
|
||||||
if isinstance(target, ast.Subscript) and hasattr(target.value, "attr"):
|
|
||||||
if target.value.attr == "loc":
|
|
||||||
self.visit(target)
|
|
||||||
|
|
||||||
def visit_Name(self, node):
|
def visit_Name(self, node):
|
||||||
# if the name is in the mapping, update it
|
# if the name is in the mapping, update it
|
||||||
@ -154,11 +138,14 @@ class NameUpdater(ast.NodeTransformer):
|
|||||||
# do not update the names in import statements
|
# do not update the names in import statements
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
# This function is currently never successfully triggered
|
||||||
|
# since freqtrade currently only allows valid code to be processed.
|
||||||
|
# The module .hyper does not anymore exist and by that fails to even
|
||||||
|
# reach this function to be updated currently.
|
||||||
def visit_ImportFrom(self, node):
|
def visit_ImportFrom(self, node):
|
||||||
# do not update the names in import statements
|
# if hasattr(node, "module"):
|
||||||
if hasattr(node, "module"):
|
# if node.module == "freqtrade.strategy.hyper":
|
||||||
if node.module == "freqtrade.strategy.hyper":
|
# node.module = "freqtrade.strategy"
|
||||||
node.module = "freqtrade.strategy"
|
|
||||||
return node
|
return node
|
||||||
|
|
||||||
def visit_FunctionDef(self, node):
|
def visit_FunctionDef(self, node):
|
||||||
@ -210,6 +197,10 @@ class NameUpdater(ast.NodeTransformer):
|
|||||||
if hasattr(node.slice, "value"):
|
if hasattr(node.slice, "value"):
|
||||||
if hasattr(node.slice.value, "elts"):
|
if hasattr(node.slice.value, "elts"):
|
||||||
self.visit_slice_elts(node.slice.value.elts)
|
self.visit_slice_elts(node.slice.value.elts)
|
||||||
|
# Check if the target is a Subscript object with a "value" attribute
|
||||||
|
# if isinstance(target, ast.Subscript) and hasattr(target.value, "attr"):
|
||||||
|
# if target.value.attr == "loc":
|
||||||
|
# self.visit(target)
|
||||||
return node
|
return node
|
||||||
|
|
||||||
# elts can have elts (technically recursively)
|
# elts can have elts (technically recursively)
|
||||||
|
56
tests/test_strategy_updater.py
Normal file
56
tests/test_strategy_updater.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# pragma pylint: disable=missing-docstring, protected-access, invalid-name
|
||||||
|
|
||||||
|
from freqtrade.strategy.strategyupdater import StrategyUpdater
|
||||||
|
|
||||||
|
|
||||||
|
def test_strategy_updater(default_conf, caplog) -> None:
|
||||||
|
modified_code1 = StrategyUpdater.update_code(StrategyUpdater, """
|
||||||
|
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
|
||||||
|
""")
|
||||||
|
|
||||||
|
modified_code2 = StrategyUpdater.update_code(StrategyUpdater, """
|
||||||
|
buy_some_parameter = IntParameter(space='buy')
|
||||||
|
sell_some_parameter = IntParameter(space='sell')
|
||||||
|
ticker_interval = '15m'
|
||||||
|
""")
|
||||||
|
modified_code3 = StrategyUpdater.update_code(StrategyUpdater, """
|
||||||
|
use_sell_signal = True
|
||||||
|
sell_profit_only = True
|
||||||
|
sell_profit_offset = True
|
||||||
|
ignore_roi_if_buy_signal = True
|
||||||
|
forcebuy_enable = True
|
||||||
|
""")
|
||||||
|
modified_code4 = StrategyUpdater.update_code(StrategyUpdater, """
|
||||||
|
dataframe.loc[reduce(lambda x, y: x & y, conditions), 'buy'] = 1
|
||||||
|
dataframe.loc[reduce(lambda x, y: x & y, conditions), 'sell'] = 1
|
||||||
|
""")
|
||||||
|
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
|
||||||
|
|
||||||
|
assert "timeframe" in modified_code2
|
||||||
|
# check for not editing hyperopt spaces
|
||||||
|
assert "space='buy'" in modified_code2
|
||||||
|
assert "space='sell'" in modified_code2
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
assert "enter_long" in modified_code4
|
||||||
|
assert "exit_long" in modified_code4
|
Loading…
Reference in New Issue
Block a user