@@ -221,6 +221,45 @@ def _accepts_strategy_kwarg(
221221 )
222222
223223
224+ def _split_strategy_kwargs (
225+ strategy_input : Union [Type [Strategy ], Strategy , Callable [[Any , Bar ], None ], None ],
226+ strategy_kwargs : Dict [str , Any ],
227+ ) -> Tuple [Dict [str , Any ], List [str ]]:
228+ """Split kwargs into constructor-accepted kwargs and unknown keys."""
229+ if not isinstance (strategy_input , type ) or not issubclass (strategy_input , Strategy ):
230+ return strategy_kwargs , []
231+
232+ try :
233+ signature = inspect .signature (strategy_input .__init__ )
234+ except (TypeError , ValueError ):
235+ return strategy_kwargs , []
236+
237+ supports_var_kwargs = any (
238+ parameter .kind == inspect .Parameter .VAR_KEYWORD
239+ for parameter in signature .parameters .values ()
240+ )
241+ if supports_var_kwargs :
242+ return strategy_kwargs , []
243+
244+ accepted_names = {
245+ parameter_name
246+ for parameter_name , parameter in signature .parameters .items ()
247+ if parameter_name != "self"
248+ and parameter .kind
249+ in {
250+ inspect .Parameter .POSITIONAL_OR_KEYWORD ,
251+ inspect .Parameter .KEYWORD_ONLY ,
252+ }
253+ }
254+ accepted_kwargs = {
255+ key : value for key , value in strategy_kwargs .items () if key in accepted_names
256+ }
257+ unknown_keys = sorted (
258+ key for key in strategy_kwargs .keys () if key not in accepted_names
259+ )
260+ return accepted_kwargs , unknown_keys
261+
262+
224263def _maybe_warn_deprecated_symbol_argument (
225264 * ,
226265 symbol : Union [str , List [str ]],
@@ -478,6 +517,7 @@ def _load_data_map_from_adapter(
478517def _build_strategy_instance (
479518 strategy : Union [Type [Strategy ], Strategy , Callable [[Any , Bar ], None ], None ],
480519 strategy_kwargs : Dict [str , Any ],
520+ strict_strategy_params : bool ,
481521 logger : Any ,
482522 initialize : Optional [Callable [[Any ], None ]],
483523 on_start : Optional [Callable [[Any ], None ]],
@@ -489,9 +529,32 @@ def _build_strategy_instance(
489529 context : Optional [Dict [str , Any ]],
490530) -> Strategy :
491531 if isinstance (strategy , type ) and issubclass (strategy , Strategy ):
532+ accepted_kwargs , unknown_keys = _split_strategy_kwargs (
533+ strategy , strategy_kwargs
534+ )
535+ if unknown_keys :
536+ unknown_keys_text = ", " .join (unknown_keys )
537+ if strict_strategy_params :
538+ raise TypeError (
539+ "Unknown strategy constructor parameter(s): "
540+ f"{ unknown_keys_text } . Strategy={ strategy .__module__ } ."
541+ f"{ strategy .__name__ } "
542+ )
543+ logger .warning (
544+ "Ignoring unknown strategy constructor parameter(s): %s. "
545+ "Strategy=%s.%s" ,
546+ unknown_keys_text ,
547+ strategy .__module__ ,
548+ strategy .__name__ ,
549+ )
492550 try :
493- return cast (Strategy , strategy (** strategy_kwargs ))
551+ return cast (Strategy , strategy (** accepted_kwargs ))
494552 except TypeError as e :
553+ if strict_strategy_params :
554+ raise TypeError (
555+ "Failed to instantiate strategy with provided parameters: "
556+ f"{ e } . Strategy={ strategy .__module__ } .{ strategy .__name__ } "
557+ ) from e
495558 logger .warning (
496559 f"Failed to instantiate strategy with provided parameters: { e } . "
497560 "Falling back to default constructor (no arguments)."
@@ -752,6 +815,7 @@ def run_backtest(
752815 broker_profile : Optional [str ] = None ,
753816 timer_execution_policy : str = "same_cycle" ,
754817 fill_policy : Optional [FillPolicy ] = None ,
818+ strict_strategy_params : bool = True ,
755819 ** kwargs : Any ,
756820) -> BacktestResult :
757821 """
@@ -793,6 +857,9 @@ def run_backtest(
793857 "temporal": "same_cycle|next_event"}。
794858 预留未实现 price_basis: mid_quote、vwap_window、twap_window。
795859 若提供该参数,则其语义优先于 execution_mode 与 timer_execution_policy。
860+ :param strict_strategy_params: 是否严格校验策略构造参数。True 时若参数不匹配将抛错;
861+ False 时保持兼容行为(忽略未知参数并在失败时
862+ 回退无参构造)。
796863 :param timezone: 时区名称 (默认 "Asia/Shanghai")
797864 :param t_plus_one: 是否启用 T+1 交易规则 (默认 False)
798865 :param initialize: 初始化回调函数 (仅当 strategy 为函数时使用)
@@ -1191,18 +1258,6 @@ def wrapped_stream_on_event(event: BacktestStreamEvent) -> None:
11911258 if config and config .end_time :
11921259 end_time = config .end_time
11931260
1194- # Update kwargs if needed by strategy (optional, can be removed if strategies
1195- # don't need it)
1196- if start_time :
1197- kwargs ["start_time" ] = start_time
1198- if end_time :
1199- kwargs ["end_time" ] = end_time
1200-
1201- # 注意: initial_cash, commission_rate, timezone, show_progress, history_depth
1202- # 已经在上方通过优先级逻辑处理过了,这里不需要再覆盖
1203-
1204- # Risk Config injection handled later
1205-
12061261 # Handle strategy_params explicitly
12071262 if "strategy_params" in kwargs :
12081263 s_params = kwargs .pop ("strategy_params" )
@@ -1233,6 +1288,10 @@ def wrapped_stream_on_event(event: BacktestStreamEvent) -> None:
12331288 strategy_loader_options = strategy_loader_options ,
12341289 )
12351290 strategy_kwargs = dict (kwargs )
1291+ if start_time and _accepts_strategy_kwarg (strategy_input , "start_time" ):
1292+ strategy_kwargs ["start_time" ] = start_time
1293+ if end_time and _accepts_strategy_kwarg (strategy_input , "end_time" ):
1294+ strategy_kwargs ["end_time" ] = end_time
12361295 if (
12371296 symbols is not None
12381297 and "symbols" not in strategy_kwargs
@@ -1242,6 +1301,7 @@ def wrapped_stream_on_event(event: BacktestStreamEvent) -> None:
12421301 strategy_instance = _build_strategy_instance (
12431302 strategy_input ,
12441303 strategy_kwargs ,
1304+ strict_strategy_params ,
12451305 logger ,
12461306 initialize ,
12471307 on_start ,
@@ -1263,9 +1323,16 @@ def wrapped_stream_on_event(event: BacktestStreamEvent) -> None:
12631323 slot_strategy_input , "symbols"
12641324 ):
12651325 slot_strategy_kwargs ["symbols" ] = symbols
1326+ if start_time and _accepts_strategy_kwarg (
1327+ slot_strategy_input , "start_time"
1328+ ):
1329+ slot_strategy_kwargs ["start_time" ] = start_time
1330+ if end_time and _accepts_strategy_kwarg (slot_strategy_input , "end_time" ):
1331+ slot_strategy_kwargs ["end_time" ] = end_time
12661332 slot_strategy_instances [slot_key_str ] = _build_strategy_instance (
12671333 slot_strategy_input ,
12681334 slot_strategy_kwargs ,
1335+ strict_strategy_params ,
12691336 logger ,
12701337 initialize ,
12711338 on_start ,
@@ -3185,6 +3252,7 @@ def wrapped_stream_on_event(event: BacktestStreamEvent) -> None:
31853252 slot_strategy_instances [slot_key_str ] = _build_strategy_instance (
31863253 slot_strategy_input ,
31873254 {},
3255+ False ,
31883256 logger ,
31893257 None ,
31903258 None ,
0 commit comments