mirror of
https://ghfast.top/https://github.com/aeroxw/easy-tdx.git
synced 2026-09-12 19:14:16 +08:00
fix: 寻优跳过参数范围/对比支持组合/日线x轴年份/寻优跳转填充参数
4 个独立修复: 1. 寻优跳过参数范围检查:Param.validate 加 skip_bounds 参数, RegisteredStrategy.build 透传,ParamGridOptimizer 传 skip_bounds=True。 修复寻优时 fast=250 被 max_value=60 拦截的问题(探索超范围值是寻优目的)。 2. 对比页支持组合回测:CompareView 新增 extractComparable() 统一提取净值+指标, 支持单标的(performance/equity_curve)和组合(total_performance/combined_equity)。 修复勾选组合回测任务报「非单标的回测」错误。 3. K线x轴日线显示完整年份:isIntraday 判断从 length>10 改为检查时分秒非零 (日线归一化后带 T00:00:00 后缀,长度也 >10 导致误判为分钟线,slice 砍年份)。 4. 寻优跳转填充参数:BacktestView 加 useRoute(),onMounted 读 query.strategy + query.params,await nextTick 后覆盖(避免 StrategyPicker watch 重置)。 修复寻优点「查看」跳转后参数未填充、仍用默认值的问题。 测试:823 passed, mypy 211 files OK, vue-tsc OK
This commit is contained in:
@@ -143,7 +143,7 @@ class ParamGridOptimizer:
|
||||
strategy_name: str,
|
||||
param_grid: dict[str, list[Any]],
|
||||
df: pd.DataFrame,
|
||||
cash: float = 100_000.0,
|
||||
cash: float = 1_000_000.0,
|
||||
commission: float = 0.0003,
|
||||
min_commission: float = 5.0,
|
||||
stamp_tax: float = 0.001,
|
||||
@@ -181,7 +181,8 @@ class ParamGridOptimizer:
|
||||
for combo in itertools.product(*value_lists):
|
||||
params = dict(zip(param_names, combo, strict=True))
|
||||
try:
|
||||
strategy = entry.build(params)
|
||||
# 寻优时跳过参数范围检查——探索超范围值是寻优的目的
|
||||
strategy = entry.build(params, skip_bounds=True)
|
||||
engine = BacktestEngine(
|
||||
strategy=strategy,
|
||||
cash=self._cash,
|
||||
|
||||
@@ -114,7 +114,7 @@ class Param:
|
||||
schema["description"] = self.description
|
||||
return schema
|
||||
|
||||
def validate(self, value: Any) -> Any:
|
||||
def validate(self, value: Any, *, skip_bounds: bool = False) -> Any:
|
||||
"""校验并强制转换取值,不合法抛 ValueError。
|
||||
|
||||
NaN/Inf 的 float 输入会被拒绝(NaN 绕过所有比较,Inf 仅在有界时被拦,
|
||||
@@ -150,14 +150,25 @@ class Param:
|
||||
f"参数 '{self.name}' 期望 {self.type.__name__},得到 {value!r}"
|
||||
) from exc
|
||||
|
||||
if self.choices is not None and self.type is str and converted not in self.choices:
|
||||
raise ValueError(
|
||||
f"参数 '{self.name}' 取值 {converted!r} 不在可选范围 {list(self.choices)} 内"
|
||||
)
|
||||
if self.type in (int, float) and self.min_value is not None and converted < self.min_value:
|
||||
raise ValueError(f"参数 '{self.name}'={converted} 小于下限 {self.min_value}")
|
||||
if self.type in (int, float) and self.max_value is not None and converted > self.max_value:
|
||||
raise ValueError(f"参数 '{self.name}'={converted} 大于上限 {self.max_value}")
|
||||
# skip_bounds=True 时跳过范围/取值集合检查(供寻优器探索超范围值),
|
||||
# 仍保留类型转换 + NaN/Inf 拦截。
|
||||
if not skip_bounds:
|
||||
if self.choices is not None and self.type is str and converted not in self.choices:
|
||||
raise ValueError(
|
||||
f"参数 '{self.name}' 取值 {converted!r} 不在可选范围 {list(self.choices)} 内"
|
||||
)
|
||||
if (
|
||||
self.type in (int, float)
|
||||
and self.min_value is not None
|
||||
and converted < self.min_value
|
||||
):
|
||||
raise ValueError(f"参数 '{self.name}'={converted} 小于下限 {self.min_value}")
|
||||
if (
|
||||
self.type in (int, float)
|
||||
and self.max_value is not None
|
||||
and converted > self.max_value
|
||||
):
|
||||
raise ValueError(f"参数 '{self.name}'={converted} 大于上限 {self.max_value}")
|
||||
return converted
|
||||
|
||||
|
||||
@@ -180,10 +191,11 @@ class ParametrizedStrategy(Strategy):
|
||||
# 实例属性:已校验的参数值字典(init/next 中通过 self.p[name] 访问)。
|
||||
p: dict[str, Any]
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
def __init__(self, *, skip_bounds: bool = False, **kwargs: Any) -> None:
|
||||
"""从 kwargs 构造策略参数。
|
||||
|
||||
多余的未知参数抛 ValueError;缺失参数取默认值。
|
||||
skip_bounds=True 时跳过参数范围检查(供寻优器探索超范围值)。
|
||||
"""
|
||||
super().__init__()
|
||||
declared = {param.name: param for param in self.params}
|
||||
@@ -194,7 +206,7 @@ class ParametrizedStrategy(Strategy):
|
||||
resolved: dict[str, Any] = {}
|
||||
for name, param in declared.items():
|
||||
raw = kwargs.get(name, param.default)
|
||||
resolved[name] = param.validate(raw)
|
||||
resolved[name] = param.validate(raw, skip_bounds=skip_bounds)
|
||||
self.p = resolved
|
||||
|
||||
|
||||
@@ -220,9 +232,17 @@ class RegisteredStrategy:
|
||||
"params": [p.to_schema() for p in self.params],
|
||||
}
|
||||
|
||||
def build(self, params: dict[str, Any] | None = None) -> ParametrizedStrategy:
|
||||
"""用给定参数构造策略实例,缺失参数取默认值。"""
|
||||
return self.strategy_cls(**(params or {}))
|
||||
def build(
|
||||
self,
|
||||
params: dict[str, Any] | None = None,
|
||||
*,
|
||||
skip_bounds: bool = False,
|
||||
) -> ParametrizedStrategy:
|
||||
"""用给定参数构造策略实例,缺失参数取默认值。
|
||||
|
||||
skip_bounds=True 时跳过参数范围检查(供寻优器探索超范围值)。
|
||||
"""
|
||||
return self.strategy_cls(**(params or {}), skip_bounds=skip_bounds)
|
||||
|
||||
|
||||
class StrategyRegistry:
|
||||
|
||||
Reference in New Issue
Block a user