1- import re
2- from .algorithm import Algorithm
3- from investing_algorithm_framework .domain import OperationalException
4-
5-
6- def validate_algorithm_name (name , illegal_chars = r"[\/:*?\"<>|]" ):
7- """
8- Validate an algorithm name for illegal characters and throw an
9- exception if any are found.
10-
11- Args:
12- name (str): The name to validate.
13- illegal_chars (str): A regex pattern for characters considered
14- illegal (default: r"[/:*?\" <>|]").
1+ import inspect
152
16- Raises:
17- ValueError: If illegal characters are found in the filename.
18- """
19- if re .search (illegal_chars , name ):
20- raise OperationalException (
21- f"Illegal characters detected in filename: { name } . "
22- f"Illegal characters: { illegal_chars } "
23- )
3+ from .algorithm import Algorithm
4+ from investing_algorithm_framework .app .strategy import TradingStrategy
5+ from investing_algorithm_framework .domain import generate_algorithm_id
246
257
268class AlgorithmFactory :
@@ -29,86 +11,108 @@ class AlgorithmFactory:
2911 """
3012
3113 @staticmethod
32- def create_algorithm_name ( algorithm ):
14+ def _instantiate_strategy ( strategy ):
3315 """
34- Create a name for the algorithm based on its
35- strategies.
16+ Instantiate a strategy if it's a class, otherwise return as-is.
3617
3718 Args:
38- algorithm (Algorithm): Instance of Algorithm .
19+ strategy: Either a TradingStrategy class or instance .
3920
4021 Returns:
41- str: Name of the algorithm .
22+ TradingStrategy instance .
4223 """
43- first_strategy = algorithm .strategies [0 ] \
44- if algorithm .strategies else None
45-
46- if first_strategy is not None :
47- return f"{ first_strategy .__class__ .__name__ } "
48-
49- return "DefaultAlgorithm"
24+ if inspect .isclass (strategy ):
25+ if issubclass (strategy , TradingStrategy ):
26+ return strategy ()
27+ return strategy
5028
5129 @staticmethod
5230 def create_algorithm (
53- name = None ,
5431 algorithm = None ,
5532 strategy = None ,
5633 strategies = None ,
5734 tasks = None ,
5835 on_strategy_run_hooks = None ,
5936 ) -> Algorithm :
6037 """
61- Create an instance of the specified algorithm type.
38+ Create an instance of an Algorithm with the given parameters.
39+
40+ If the app already has strategies, tasks, or hooks defined,
41+ they will be merged with the provided ones.
42+
43+ If there is no algorithm id provided, an id will be generated and
44+ also set to each strategy within the algorithm.
6245
6346 Args:
64- name (str): Name of the algorithm.
6547 algorithm (Algorithm): Instance of Algorithm to be used.
66- strategy (TradingStrategy): Single TradingStrategy instance.
67- strategies (list): List of TradingStrategy instances.
48+ strategy (TradingStrategy): Single TradingStrategy instance
49+ or class.
50+ strategies (list): List of TradingStrategy instances or classes.
6851 tasks (list): List of Task instances.
6952 on_strategy_run_hooks (list): List of hooks to be called
7053 when a strategy is run.
7154
7255 Returns:
7356 Algorithm: Instance of Algorithm.
7457 """
75- name = name
76- strategies = strategies or []
58+ final_strategies = []
7759 tasks = tasks or []
7860 on_strategy_run_hooks = on_strategy_run_hooks or []
79- data_sources = []
61+ algorithm_id = None
8062
63+ # First, process algorithm if provided
8164 if algorithm is not None and isinstance (algorithm , Algorithm ):
82- if name is None :
83- name = algorithm .name
84-
85- strategies .extend (algorithm .strategies )
65+ final_strategies .extend (algorithm .strategies )
8666 tasks .extend (algorithm .tasks )
8767 on_strategy_run_hooks .extend (algorithm .on_strategy_run_hooks )
88-
89- if hasattr (algorithm , 'data_sources' ):
90- data_sources .extend (algorithm .data_sources )
91-
68+ algorithm_id = algorithm .algorithm_id
69+
70+ # Then, add strategies from the strategies list
71+ if strategies is not None :
72+ for strat in strategies :
73+ # Instantiate if it's a class
74+ strat = AlgorithmFactory ._instantiate_strategy (strat )
75+ # Avoid duplicates by checking strategy_id
76+ if not any (
77+ s .strategy_id == strat .strategy_id
78+ for s in final_strategies
79+ ):
80+ final_strategies .append (strat )
81+
82+ # Finally, add single strategy if provided and not already in list
9283 if strategy is not None :
93- strategies .append (strategy )
94- data_sources .extend (strategy .data_sources )
95-
96- for strategy_entry in strategies :
97- if strategy_entry .data_sources is not None \
98- and len (strategy_entry .data_sources ):
99- data_sources .extend (strategy_entry .data_sources )
84+ # Instantiate if it's a class
85+ strategy = AlgorithmFactory ._instantiate_strategy (strategy )
86+ if not any (
87+ s .strategy_id == strategy .strategy_id
88+ for s in final_strategies
89+ ):
90+ final_strategies .append (strategy )
91+
92+ # Collect data sources from all strategies (avoiding duplicates)
93+ data_sources = []
94+ seen_data_source_ids = set ()
95+
96+ for strategy_entry in final_strategies :
97+ if strategy_entry .data_sources is not None :
98+ for ds in strategy_entry .data_sources :
99+ ds_id = ds .get_identifier () \
100+ if hasattr (ds , 'get_identifier' ) else id (ds )
101+ if ds_id not in seen_data_source_ids :
102+ data_sources .append (ds )
103+ seen_data_source_ids .add (ds_id )
104+
105+ # Generate algorithm_id if not provided
106+ if algorithm_id is None and len (final_strategies ) > 0 :
107+ algorithm_id = generate_algorithm_id (
108+ strategy = final_strategies [0 ]
109+ )
100110
101111 algorithm = Algorithm (
102- name = name ,
103- strategies = strategies ,
112+ algorithm_id = algorithm_id ,
113+ strategies = final_strategies ,
104114 tasks = tasks ,
105115 on_strategy_run_hooks = on_strategy_run_hooks ,
106116 data_sources = data_sources
107117 )
108-
109- if algorithm .name is None :
110- algorithm .name = AlgorithmFactory .create_algorithm_name (algorithm )
111-
112- # Validate the algorithm name
113- validate_algorithm_name (algorithm .name )
114118 return algorithm
0 commit comments