|
43 | 43 | "negative": -3, |
44 | 44 | } |
45 | 45 |
|
| 46 | +HORIZON_WINDOWS = { |
| 47 | + "short": "1-10个交易日", |
| 48 | + "medium": "2-12周", |
| 49 | + "long": "1-3年", |
| 50 | + "not_applicable": "不适用", |
| 51 | +} |
| 52 | + |
46 | 53 |
|
47 | 54 | @dataclass(frozen=True) |
48 | 55 | class Event: |
@@ -151,12 +158,12 @@ def horizon_fit(style: str, rating: str) -> tuple[str, list[str], str, str]: |
151 | 158 | if rating in {"verify_source", "defer", "monitor"}: |
152 | 159 | return "not_applicable", ["not_applicable"], "不适用", "该项不是当前推荐,仅用于来源核验、风险暂缓或持续监控。" |
153 | 160 | if style == "event_driven": |
154 | | - return "medium", ["short", "medium"], "中线", "事件驱动以中线验证为主;短线只适合观察催化反应,波动和反转风险更高。" |
| 161 | + return "medium", ["short", "medium"], "中线", "事件驱动主周期为2-12周;1-10个交易日只适合观察催化反应,波动和反转风险更高。" |
155 | 162 | if style in {"long_horizon_growth", "value_quality"}: |
156 | | - return "long", ["medium", "long"], "长线", "更适合用中长期基本面和趋势验证,不适合按短线噪声频繁切换。" |
| 163 | + return "long", ["medium", "long"], "长线", "主周期为1-3年,更适合用基本面、产业趋势和事件持续性验证;超过3年需要年度复盘确认逻辑仍成立。" |
157 | 164 | if style == "macro_context": |
158 | | - return "medium", ["medium"], "中线", "宏观和政策背景更适合中线观察,需要结合市场环境复核。" |
159 | | - return "medium", ["medium"], "中线", "默认按中线研究处理,等待更多证据后再调整周期判断。" |
| 165 | + return "medium", ["medium"], "中线", "主周期为2-12周,宏观和政策背景需要结合市场环境复核。" |
| 166 | + return "medium", ["medium"], "中线", "默认按2-12周中线研究处理,等待更多证据后再调整周期判断。" |
160 | 167 |
|
161 | 168 |
|
162 | 169 | def rating_label(rating: str) -> str: |
@@ -273,6 +280,8 @@ def build_recommendation( |
273 | 280 | style = strategy_style(item, events, ai_bias) |
274 | 281 | score = round(clamp((evidence_score - risk_score + 8) / 24, 0.05, 0.85), 2) |
275 | 282 | primary_horizon, suitable_horizons, primary_horizon_label, horizon_note = horizon_fit(style, rating) |
| 283 | + primary_horizon_window = HORIZON_WINDOWS[primary_horizon] |
| 284 | + suitable_horizon_windows = {horizon: HORIZON_WINDOWS[horizon] for horizon in suitable_horizons} |
276 | 285 | confidence, confidence_label = source_confidence(events) |
277 | 286 | tier, tier_label = recommendation_tier(rating, score, confidence) |
278 | 287 |
|
@@ -305,8 +314,10 @@ def build_recommendation( |
305 | 314 | "recommendation_tier_label": tier_label, |
306 | 315 | "primary_horizon": primary_horizon, |
307 | 316 | "primary_horizon_label": primary_horizon_label, |
| 317 | + "primary_horizon_window": primary_horizon_window, |
308 | 318 | "horizon_note": horizon_note, |
309 | 319 | "suitable_horizons": suitable_horizons, |
| 320 | + "suitable_horizon_windows": suitable_horizon_windows, |
310 | 321 | "strategy_style": style, |
311 | 322 | "score": score, |
312 | 323 | "evidence_score": int(evidence_score), |
@@ -371,7 +382,7 @@ def build_advisory_report( |
371 | 382 | recommendations = recommendations[:max_candidates] |
372 | 383 |
|
373 | 384 | report = { |
374 | | - "schema_version": "4", |
| 385 | + "schema_version": "5", |
375 | 386 | "as_of": as_of_date.isoformat(), |
376 | 387 | "generated_at": utc_now_iso(), |
377 | 388 | "mode": "model_recommendations", |
@@ -430,7 +441,11 @@ def render_markdown(report: dict[str, Any]) -> str: |
430 | 441 | f"### {rec['symbol']} - {rec['rating_label']}", |
431 | 442 | "", |
432 | 443 | f"- 推荐层级: `{rec['recommendation_tier_label']}`", |
433 | | - f"- 适合周期: `{rec['primary_horizon_label']}`", |
| 444 | + f"- 适合周期: `{rec['primary_horizon_label']}({rec['primary_horizon_window']})`", |
| 445 | + "- 可观察周期: " |
| 446 | + + ", ".join( |
| 447 | + f"`{horizon}={window}`" for horizon, window in rec["suitable_horizon_windows"].items() |
| 448 | + ), |
434 | 449 | f"- 周期说明: {rec['horizon_note']}", |
435 | 450 | f"- Strategy style: `{rec['strategy_style']}`", |
436 | 451 | f"- Model score: `{rec['score']}`", |
|
0 commit comments