removed prints for strategy could not be loaded
Changed logic to contain much less if conditions currently still missing: Webhook terminology, Telegram notification settings, Strategy/Config settings
This commit is contained in:
parent
71ec32ac9e
commit
ed55296d20
@ -3,6 +3,8 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import astor
|
||||||
|
|
||||||
|
|
||||||
class StrategyUpdater:
|
class StrategyUpdater:
|
||||||
name_mapping = {
|
name_mapping = {
|
||||||
@ -79,7 +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(tree)
|
updated_code = self.modify_ast(self, tree)
|
||||||
|
|
||||||
# return the modified code without executing it
|
# return the modified code without executing it
|
||||||
return updated_code
|
return updated_code
|
||||||
@ -94,67 +96,67 @@ class StrategyUpdater:
|
|||||||
ast.increment_lineno(tree, n=1)
|
ast.increment_lineno(tree, n=1)
|
||||||
|
|
||||||
# generate the new code from the updated AST
|
# generate the new code from the updated AST
|
||||||
return ast.dump(tree)
|
return astor.to_source(tree)
|
||||||
|
|
||||||
|
|
||||||
# Here we go through each respective node, slice, elt, key ... to replace outdated entries.
|
# Here we go through each respective node, slice, elt, key ... to replace outdated entries.
|
||||||
class NameUpdater(ast.NodeTransformer):
|
class NameUpdater(ast.NodeTransformer):
|
||||||
def generic_visit(self, node):
|
def generic_visit(self, node):
|
||||||
# traverse the AST recursively by calling the visitor method for each child node
|
|
||||||
if hasattr(node, "_fields"):
|
|
||||||
for field_name, field_value in ast.iter_fields(node):
|
|
||||||
self.check_strategy_and_config_settings(node, field_value)
|
|
||||||
self.check_fields(field_value)
|
|
||||||
for child in ast.iter_child_nodes(node):
|
|
||||||
self.generic_visit(child)
|
|
||||||
|
|
||||||
def check_fields(self, field_value):
|
# space is not yet transferred from buy/sell to entry/exit and thereby has to be skipped.
|
||||||
if isinstance(field_value, list):
|
if isinstance(node, ast.keyword):
|
||||||
for item in field_value:
|
if node.arg == "space":
|
||||||
if (isinstance(item, ast.AST) or isinstance(item, ast.If) or
|
return node
|
||||||
isinstance(item, ast.Expr)):
|
|
||||||
self.visit(item)
|
|
||||||
if isinstance(field_value, ast.Name):
|
|
||||||
self.visit_Name(field_value)
|
|
||||||
|
|
||||||
def check_strategy_and_config_settings(self, node, field_value):
|
# from here on this is the original function.
|
||||||
if (isinstance(field_value, ast.AST) and
|
for field, old_value in ast.iter_fields(node):
|
||||||
hasattr(node, "targets") and
|
if isinstance(old_value, list):
|
||||||
isinstance(node.targets, list)):
|
new_values = []
|
||||||
for target in node.targets:
|
for value in old_value:
|
||||||
if (hasattr(target, "id") and
|
if isinstance(value, ast.AST):
|
||||||
hasattr(field_value, "keys") and
|
value = self.visit(value)
|
||||||
isinstance(field_value.keys, list)):
|
if value is None:
|
||||||
if (target.id == "order_time_in_force" or
|
continue
|
||||||
target.id == "order_types" or
|
elif not isinstance(value, ast.AST):
|
||||||
target.id == "unfilledtimeout"):
|
new_values.extend(value)
|
||||||
for key in field_value.keys:
|
continue
|
||||||
self.visit(key)
|
new_values.append(value)
|
||||||
|
old_value[:] = new_values
|
||||||
|
elif isinstance(old_value, ast.AST):
|
||||||
|
new_node = self.visit(old_value)
|
||||||
|
if new_node is None:
|
||||||
|
delattr(node, field)
|
||||||
|
else:
|
||||||
|
setattr(node, field, new_node)
|
||||||
|
return node
|
||||||
|
|
||||||
def check_args(self, node):
|
def visit_Expr(self, node):
|
||||||
if isinstance(node.args, ast.arguments):
|
node.value.left.id = self.check_dict(StrategyUpdater.name_mapping, node.value.left.id)
|
||||||
self.check_args(node.args)
|
self.visit(node.value)
|
||||||
if hasattr(node, "args"):
|
return node
|
||||||
|
|
||||||
|
# Renames an element if contained inside a dictionary.
|
||||||
|
@staticmethod
|
||||||
|
def check_dict(current_dict: dict, element: str):
|
||||||
|
if element in current_dict:
|
||||||
|
element = current_dict[element]
|
||||||
|
return element
|
||||||
|
|
||||||
|
def visit_arguments(self, node):
|
||||||
if isinstance(node.args, list):
|
if isinstance(node.args, list):
|
||||||
for arg in node.args:
|
for arg in node.args:
|
||||||
if arg.arg in StrategyUpdater.name_mapping:
|
arg.arg = self.check_dict(StrategyUpdater.name_mapping, arg.arg)
|
||||||
arg.arg = StrategyUpdater.name_mapping[arg.arg]
|
|
||||||
return node
|
return node
|
||||||
|
|
||||||
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
|
||||||
if node.id in StrategyUpdater.name_mapping:
|
node.id = self.check_dict(StrategyUpdater.name_mapping, node.id)
|
||||||
node.id = StrategyUpdater.name_mapping[node.id]
|
|
||||||
return node
|
return node
|
||||||
|
|
||||||
def visit_Import(self, node):
|
def visit_Import(self, node):
|
||||||
# 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):
|
||||||
# if hasattr(node, "module"):
|
# if hasattr(node, "module"):
|
||||||
# if node.module == "freqtrade.strategy.hyper":
|
# if node.module == "freqtrade.strategy.hyper":
|
||||||
@ -164,33 +166,21 @@ class NameUpdater(ast.NodeTransformer):
|
|||||||
def visit_If(self, node: ast.If):
|
def visit_If(self, node: ast.If):
|
||||||
for child in ast.iter_child_nodes(node):
|
for child in ast.iter_child_nodes(node):
|
||||||
self.visit(child)
|
self.visit(child)
|
||||||
return self.generic_visit(node)
|
return node
|
||||||
|
|
||||||
def visit_FunctionDef(self, node):
|
def visit_FunctionDef(self, node):
|
||||||
# if the function name is in the mapping, update it
|
node.name = self.check_dict(StrategyUpdater.function_mapping, node.name)
|
||||||
if node.name in StrategyUpdater.function_mapping:
|
self.generic_visit(node)
|
||||||
node.name = StrategyUpdater.function_mapping[node.name]
|
|
||||||
if hasattr(node, "args"):
|
|
||||||
self.check_args(node)
|
|
||||||
return self.generic_visit(node)
|
|
||||||
|
|
||||||
def visit_Assign(self, node):
|
|
||||||
if hasattr(node, "targets") and isinstance(node.targets, list):
|
|
||||||
for target in node.targets:
|
|
||||||
if hasattr(target, "id") and target.id in StrategyUpdater.name_mapping:
|
|
||||||
target.id = StrategyUpdater.name_mapping[target.id]
|
|
||||||
return node
|
return node
|
||||||
|
|
||||||
def visit_Attribute(self, node):
|
def visit_Attribute(self, node):
|
||||||
# if the attribute name is 'nr_of_successful_buys',
|
|
||||||
# update it to 'nr_of_successful_entries'
|
|
||||||
if (
|
if (
|
||||||
isinstance(node.value, ast.Name)
|
isinstance(node.value, ast.Name)
|
||||||
and node.value.id == 'trades'
|
and node.value.id == 'trades'
|
||||||
and node.attr == 'nr_of_successful_buys'
|
and node.attr == 'nr_of_successful_buys'
|
||||||
):
|
):
|
||||||
node.attr = 'nr_of_successful_entries'
|
node.attr = 'nr_of_successful_entries'
|
||||||
return self.generic_visit(node)
|
return node
|
||||||
|
|
||||||
def visit_ClassDef(self, node):
|
def visit_ClassDef(self, node):
|
||||||
# check if the class is derived from IStrategy
|
# check if the class is derived from IStrategy
|
||||||
@ -216,7 +206,8 @@ class NameUpdater(ast.NodeTransformer):
|
|||||||
and child.targets[0].id == 'INTERFACE_VERSION'
|
and child.targets[0].id == 'INTERFACE_VERSION'
|
||||||
):
|
):
|
||||||
child.value = ast.parse('3').body[0].value
|
child.value = ast.parse('3').body[0].value
|
||||||
return self.generic_visit(node)
|
self.generic_visit(node)
|
||||||
|
return node
|
||||||
|
|
||||||
def visit_Subscript(self, node):
|
def visit_Subscript(self, node):
|
||||||
if isinstance(node.slice, ast.Constant):
|
if isinstance(node.slice, ast.Constant):
|
||||||
@ -228,10 +219,6 @@ 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_elts(node.slice.value.elts)
|
self.visit_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)
|
||||||
@ -241,6 +228,7 @@ class NameUpdater(ast.NodeTransformer):
|
|||||||
self.visit_elt(elt)
|
self.visit_elt(elt)
|
||||||
else:
|
else:
|
||||||
self.visit_elt(elts)
|
self.visit_elt(elts)
|
||||||
|
return elts
|
||||||
|
|
||||||
# sub function again needed since the structure itself is highly flexible ...
|
# sub function again needed since the structure itself is highly flexible ...
|
||||||
def visit_elt(self, elt):
|
def visit_elt(self, elt):
|
||||||
@ -254,9 +242,9 @@ class NameUpdater(ast.NodeTransformer):
|
|||||||
else:
|
else:
|
||||||
for arg in elt.args:
|
for arg in elt.args:
|
||||||
self.visit_elts(arg)
|
self.visit_elts(arg)
|
||||||
|
return elt
|
||||||
|
|
||||||
def visit_Constant(self, node):
|
def visit_Constant(self, node):
|
||||||
# do not update the names in import statements
|
node.value = self.check_dict(StrategyUpdater.otif_ot_unfilledtimeout, node.value)
|
||||||
if node.value in StrategyUpdater.otif_ot_unfilledtimeout:
|
node.value = self.check_dict(StrategyUpdater.name_mapping, node.value)
|
||||||
node.value = StrategyUpdater.otif_ot_unfilledtimeout[node.value]
|
|
||||||
return node
|
return node
|
||||||
|
@ -4,11 +4,6 @@ from freqtrade.strategy.strategyupdater import StrategyUpdater
|
|||||||
|
|
||||||
|
|
||||||
def test_strategy_updater(default_conf, caplog) -> None:
|
def test_strategy_updater(default_conf, caplog) -> None:
|
||||||
modified_code2 = StrategyUpdater.update_code(StrategyUpdater, """
|
|
||||||
ticker_interval = '15m'
|
|
||||||
buy_some_parameter = IntParameter(space='buy')
|
|
||||||
sell_some_parameter = IntParameter(space='sell')
|
|
||||||
""")
|
|
||||||
modified_code1 = StrategyUpdater.update_code(StrategyUpdater, """
|
modified_code1 = StrategyUpdater.update_code(StrategyUpdater, """
|
||||||
class testClass(IStrategy):
|
class testClass(IStrategy):
|
||||||
def populate_buy_trend():
|
def populate_buy_trend():
|
||||||
@ -21,6 +16,11 @@ class testClass(IStrategy):
|
|||||||
pass
|
pass
|
||||||
def custom_sell():
|
def custom_sell():
|
||||||
pass
|
pass
|
||||||
|
""")
|
||||||
|
modified_code2 = StrategyUpdater.update_code(StrategyUpdater, """
|
||||||
|
ticker_interval = '15m'
|
||||||
|
buy_some_parameter = IntParameter(space='buy')
|
||||||
|
sell_some_parameter = IntParameter(space='sell')
|
||||||
""")
|
""")
|
||||||
modified_code3 = StrategyUpdater.update_code(StrategyUpdater, """
|
modified_code3 = StrategyUpdater.update_code(StrategyUpdater, """
|
||||||
use_sell_signal = True
|
use_sell_signal = True
|
||||||
@ -59,11 +59,14 @@ def confirm_trade_exit(sell_reason):
|
|||||||
if (sell_reason == 'stop_loss'):
|
if (sell_reason == 'stop_loss'):
|
||||||
pass
|
pass
|
||||||
""")
|
""")
|
||||||
# modified_code8 = StrategyUpdater.update_code(StrategyUpdater, """
|
modified_code8 = StrategyUpdater.update_code(StrategyUpdater, """
|
||||||
# sell_reason == 'sell_signal'
|
sell_reason == 'sell_signal'
|
||||||
# sell_reason == 'force_sell'
|
sell_reason == 'force_sell'
|
||||||
# sell_reason == 'emergency_sell'
|
sell_reason == 'emergency_sell'
|
||||||
# """)
|
""")
|
||||||
|
|
||||||
|
# currently still missing:
|
||||||
|
# Webhook terminology, Telegram notification settings, Strategy/Config settings
|
||||||
|
|
||||||
assert "populate_entry_trend" in modified_code1
|
assert "populate_entry_trend" in modified_code1
|
||||||
assert "populate_exit_trend" in modified_code1
|
assert "populate_exit_trend" in modified_code1
|
||||||
@ -100,7 +103,7 @@ def confirm_trade_exit(sell_reason):
|
|||||||
assert "exit_reason == 'stop_loss'" in modified_code7
|
assert "exit_reason == 'stop_loss'" in modified_code7
|
||||||
|
|
||||||
# those tests currently don't work, next in line.
|
# those tests currently don't work, next in line.
|
||||||
# assert "exit_signal" in modified_code8
|
assert "exit_signal" in modified_code8
|
||||||
# assert "exit_reason" in modified_code8
|
assert "exit_reason" in modified_code8
|
||||||
# assert "force_exit" in modified_code8
|
assert "force_exit" in modified_code8
|
||||||
# assert "emergency_exit" in modified_code8
|
assert "emergency_exit" in modified_code8
|
||||||
|
Loading…
Reference in New Issue
Block a user