"""持有周期定义:把"周/双周/月/季/半年/年"或自定义交易日数,映射成
调仓间隔、动量/趋势回看窗口、反转权重。
设计原则:周期越短,越依赖短周期动量与反转;周期越长,越依赖中期动量与趋势,
并且因子回看窗口应大致等于 1~2 个持有周期,避免"用周线数据做季度决策"。
"""
from __future__ import annotations
# 周期名 -> 交易日数
PERIOD_DAYS = {
"周": 5,
"双周": 10,
"月": 20,
"季": 60,
"半年": 120,
"年": 250,
}
# 交易日数 -> 因子配置(momentum/trend 回看窗口、反转权重、对应 strategy_generator 的 horizon 名)
PERIOD_PROFILE = {
5: dict(horizon="短线", momentum=20, trend=20, reversal=0.20, label="周"),
10: dict(horizon="波段", momentum=60, trend=60, reversal=0.10, label="双周"),
20: dict(horizon="中线", momentum=120, trend=120, reversal=0.00, label="月"),
60: dict(horizon="长线", momentum=180, trend=200, reversal=0.00, label="季"),
120: dict(horizon="半年", momentum=250, trend=250, reversal=0.00, label="半年"),
250: dict(horizon="半年", momentum=250, trend=250, reversal=0.00, label="年"),
}
def resolve_period(period: str = None, days: int = None) -> dict:
"""解析周期参数,返回 {days, momentum, trend, reversal, horizon, label}。
Args:
period: 周/双周/月/季/半年/年
days: 自定义交易日数(优先于 period)
"""
if days is not None:
days = int(days)
if days < 3 or days > 250:
raise ValueError("自定义周期需在 3~250 个交易日之间")
profile = PERIOD_PROFILE.get(days)
if profile:
return dict(days=days, label=profile["label"], **{k: profile[k] for k in ("horizon", "momentum", "trend", "reversal")})
# 任意天数:按交易日数自适应(回看窗口 ≈ 2~4 个周期,限制在 20~250)
mom = int(max(20, min(250, round(days * 3))))
trend = int(max(20, min(250, round(days * 6))))
# 对应到最接近的 horizon
nearest = min(PERIOD_PROFILE, key=lambda k: abs(k - days))
return dict(days=days, label=f"自定义{days}日", horizon=PERIOD_PROFILE[nearest]["horizon"],
momentum=mom, trend=trend, reversal=0.0)
if not period:
period = "月"
if period not in PERIOD_DAYS:
raise ValueError(f"周期必须为 {list(PERIOD_DAYS)} 之一,或用 --days 指定交易日数")
d = PERIOD_DAYS[period]
profile = PERIOD_PROFILE[d]
return dict(days=d, label=period, **{k: profile[k] for k in ("horizon", "momentum", "trend", "reversal")})