Skip to content

Commit 6c25629

Browse files
committed
Clarify advisory stock pool format
1 parent af246a0 commit 6c25629

11 files changed

Lines changed: 214 additions & 77 deletions

README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -179,17 +179,17 @@ review_checklist[]
179179
```
180180

181181
`theme_first_candidates[]` is an optional display section derived from the
182-
theme momentum snapshot. It ranks symbols inside strong themes by symbol
183-
momentum first, then shows whether each candidate already has event confirmation
184-
or remains a theme-only candidate. It is still research-only and must not encode
185-
orders, target weights, or account-level advice.
182+
theme momentum snapshot. Public renderers present it as a 5-10 name "重点股票池":
183+
industry/theme background, why the name entered the pool, event-confirmation
184+
state, and key risks. It is still research-only and must not encode orders,
185+
target weights, or account-level advice.
186186

187187
Default horizon windows:
188188

189189
- short: `1-10 trading days`
190190
- medium: `2-12 weeks`
191191
- long: `1-3 years`
192-
- not_applicable: source check, defer, or monitor only
192+
- not_applicable: source check, defer, or background tracking only
193193

194194
## Versioning
195195

@@ -242,10 +242,11 @@ python scripts/build_advisory_report.py \
242242
```
243243

244244
Theme momentum is display-first context: it highlights strong themes and creates
245-
a `theme_first_candidates[]` section so AI/high-tech candidates are visible even
246-
when stable event evidence is still pending. It does not change recommendation
247-
ratings, scores, allocations, or execution policy. Workflows skip the section
248-
when the snapshot file is absent.
245+
a `theme_first_candidates[]` stock-pool section so AI/high-tech candidates are
246+
visible with industry/theme background and reasons even when stable event
247+
evidence is still pending. It does not change recommendation ratings, scores,
248+
allocations, or execution policy. Workflows skip the section when the snapshot
249+
file is absent.
249250

250251
Yahoo chart downloads are only a temporary fallback. Do not rely on random free
251252
proxy pools for the stable pipeline; prefer audited price snapshots, cache files,

README.zh-CN.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ python scripts/build_advisory_report.py \
3535
输出包括:
3636

3737
- 推荐标的
38-
- 推荐等级:重点推荐、观察、先核验来源、暂缓、监控
38+
- 推荐等级:重点推荐、观察、先核验来源、暂缓、背景跟踪
3939
- 推荐层级:一级推荐、二级推荐、观察名单、来源核验
4040
- 适合周期:短线、中线、长线,并明确时间窗口
4141
- 周期窗口:短线=1-10个交易日,中线=2-12周,长线=1-3年
@@ -158,7 +158,8 @@ python scripts/build_advisory_report.py \
158158
--output-md data/output/advisory_report.example.md
159159
```
160160

161-
主题动量会生成 `theme_first_candidates[]`,让 AI / 高科技等强主题里的候选标的先展示出来。
161+
主题动量会生成 `theme_first_candidates[]`,公开页面会把它展示为“本期重点股票池”。
162+
股票池每期保留 5-10 个股票/公司标的,说明行业/主题背景、为什么入选、事件确认状态和主要风险。
162163
这些候选按主题和个股动量排序,并标明是否已有事件确认;但它们仍然不直接改变推荐评级、分数、仓位或执行状态。
163164
线上 workflow 如果找不到 `data/output/theme_momentum_snapshot.json`,会自动跳过这个展示区块。
164165

docs/advisory_contract.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ Allowed ratings:
7070
- `defer`
7171
- `monitor`
7272

73+
Public Chinese renderers label `monitor` as `背景跟踪` to avoid implying an
74+
actionable monitoring instruction. It means context-only and not recommended.
75+
7376
Allowed recommendation tiers:
7477

7578
- `tier_1`
@@ -116,9 +119,10 @@ The contract intentionally rejects account-action fields such as `target_weight`
116119
## Theme-first Candidate
117120

118121
`theme_first_candidates` is optional and is derived from the theme momentum
119-
snapshot. It exists to make strong-theme candidates visible before the
120-
event-confirmed recommendation list. It is still research-only and must not
121-
contain account-action fields.
122+
snapshot. Public renderers present it as a 5-10 name stock pool with
123+
industry/theme background, reasons, event confirmation, and risks. It exists to
124+
make strong-theme candidates visible before the event-confirmed recommendation
125+
list. It is still research-only and must not contain account-action fields.
122126

123127
Important fields:
124128

@@ -128,10 +132,13 @@ symbol
128132
candidate_type = theme_first
129133
primary_theme_id
130134
primary_theme_name
135+
industry_background
131136
symbol_momentum_score
132137
advisor_status
133138
source_confirmation
134139
theme_ids
140+
recommendation_summary
141+
risk_summary
135142
reasons
136143
risk_notes
137144
```

docs/data_factor_roadmap.zh-CN.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ Crypto 可作为跨资产风险情绪参考,但暂时不应混入 US equity
164164
## 低风险实施顺序
165165

166166
1. 先让 `QuantAdvisorResearch` 把主题动量放到报告前面:
167-
- 新增 `theme_first_candidates[]`
167+
- 新增 `theme_first_candidates[]`,公开展示为“本期重点股票池”。
168+
- 每期保留 5-10 个股票/公司标的,说明行业/主题背景、入选原因、事件确认和主要风险。
168169
- 按主题和个股动量排序,优先展示 AI / 高科技等强主题候选。
169170
- 事件证据只作为确认项,不再让事件股默认压过强主题候选。
170171

@@ -233,6 +234,6 @@ config/symbol_theme_exposure.csv
233234

234235
1. theme membership 先固定,再观察后续表现。
235236
2. AI 只能输出 `theme_bias` 和 shadow context,不能输出目标仓位。
236-
3. Advisor 可以把主题 bias 和主题动量作为“主题优先候选”展示入口;一级事件确认推荐仍需要事件证据和来源质量支撑。
237+
3. Advisor 可以把主题 bias 和主题动量作为“本期重点股票池”展示入口;一级事件确认推荐仍需要事件证据和来源质量支撑。
237238
4. 每次规则、taxonomy、universe 变更都要记录版本,后续 walk-forward 只能 replay 已保存 artifact。
238239
5. 不因为 MU、INTC、DELL 或任何短期热门标的临时调权重;如果它们长期有 SEC/IR/政策/需求证据,会通过固定规则自然上升。

docs/notification_format.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ account suitability, or any account-specific allocation advice.
3131
主题动量:
3232
- #1 hbm_memory 分数=2.16 标的=MU
3333
34-
主题优先候选:
35-
- #1 MU | hbm_memory | 动量=2.93 | 主题候选 | 待事件确认
34+
本期重点股票池(5-10只,非个性化,不等于买入):
35+
- #1 MU | 科技 / HBM and memory cycle | 近3月 +135.6% | 事件:待事件确认 | 结论:主题候选
36+
为什么:属于科技 / HBM and memory cycle,主主题排名 #1;个股动量 2.93,近3个月 +135.6%。
3637
37-
推荐摘要
38+
推荐/观察摘要
3839
- VRT | 二级推荐 | 重点推荐 | 中线 | 分数=0.72
3940
4041
说明:非个性化模型输出;不包含下单、仓位配置或账户级建议。

docs/notification_format.zh-CN.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,11 @@ Telegram 适合中等长度,最多展示前 3-5 个推荐:
7676
主题动量:
7777
- #1 hbm_memory 分数=2.16 标的=MU
7878
79-
主题优先候选:
80-
- #1 MU | hbm_memory | 动量=2.93 | 主题候选 | 待事件确认
79+
本期重点股票池(5-10只,非个性化,不等于买入):
80+
- #1 MU | 科技 / HBM and memory cycle | 近3月 +135.6% | 事件:待事件确认 | 结论:主题候选
81+
为什么:属于科技 / HBM and memory cycle,主主题排名 #1;个股动量 2.93,近3个月 +135.6%。
8182
82-
推荐摘要
83+
推荐/观察摘要
8384
- EVT1 | 一级推荐 | 重点推荐 | 中线 | 分数=0.85
8485
- EVT2 | 一级推荐 | 重点推荐 | 中线 | 分数=0.83
8586
@@ -109,7 +110,7 @@ Subject: 量化模型推荐周度复盘 - 2026-05-30
109110
- `watch` / 观察:有线索但还需要更多事件、基本面或价格证据。
110111
- `verify_source` / 先核验来源:低置信来源,不应升级为推荐。
111112
- `defer` / 暂缓:风险或负面 shadow context 优先。
112-
- `monitor` / 监控:只保留上下文,不进入推荐区。
113+
- `monitor` / 背景跟踪:只保留上下文,不进入推荐区。
113114

114115
## 推荐层级
115116

@@ -118,7 +119,7 @@ Subject: 量化模型推荐周度复盘 - 2026-05-30
118119
- `watchlist` / 观察名单:保留观察,不作为重点推荐。
119120
- `source_check` / 来源核验:先确认来源再决定是否升级。
120121
- `defer` / 暂缓:风险优先。
121-
- `monitor` / 监控:上下文保留。
122+
- `monitor` / 背景跟踪:上下文保留,不是推荐
122123

123124
## 周期原则
124125

@@ -154,4 +155,4 @@ Subject: 量化模型推荐周度复盘 - 2026-05-30
154155
- RSS:`scripts/publish_advisory_site.py` 生成 `feed.xml`
155156
- Telegram:可选。如果仓库 secrets 配置了 `TELEGRAM_BOT_TOKEN``TELEGRAM_CHAT_ID``scripts/notify_advisory_telegram.py` 会在 Pages 部署成功后发送短摘要;如果缺少任一 secret,会跳过通知但不让发布失败。
156157

157-
通知仍然只能包含标的、主题优先候选、推荐层级、周期、来源事件数、主题动量、模型分数、理由、风险和完整报告链接;不能包含订单、目标仓位、目标股数、账户适当性或账户级配置建议。
158+
通知仍然只能包含标的、重点股票池、行业/主题背景、推荐层级、周期、来源事件数、主题动量、模型分数、理由、风险和完整报告链接;不能包含订单、目标仓位、目标股数、账户适当性或账户级配置建议。

src/quant_advisor_research/advisory_report.py

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,30 @@
5858
"monthly": "月度",
5959
}
6060

61+
SECTOR_LABELS_ZH = {
62+
"technology": "科技",
63+
"energy": "能源",
64+
"financials": "金融",
65+
"healthcare": "医疗",
66+
"industrials": "工业",
67+
"consumer": "消费",
68+
"communication_services": "通信服务",
69+
"utilities": "公用事业",
70+
}
71+
72+
EXCLUDED_THEME_PICK_SYMBOLS = {
73+
"DIA",
74+
"IWM",
75+
"QQQ",
76+
"SMH",
77+
"SOXL",
78+
"SOXX",
79+
"SPY",
80+
"TQQQ",
81+
"XLE",
82+
"XLK",
83+
}
84+
6185

6286
@dataclass(frozen=True)
6387
class Event:
@@ -196,6 +220,23 @@ def as_float(value: Any, *, default: float = 0.0) -> float:
196220
return default
197221

198222

223+
def display_number(value: Any, *, digits: int = 2) -> str:
224+
if value in {None, ""}:
225+
return "无"
226+
return f"{as_float(value):.{digits}f}"
227+
228+
229+
def display_percent(value: Any, *, digits: int = 1) -> str:
230+
if value in {None, ""}:
231+
return "无"
232+
return f"{as_float(value) * 100:+.{digits}f}%"
233+
234+
235+
def sector_label(value: Any) -> str:
236+
text = str(value or "").strip()
237+
return SECTOR_LABELS_ZH.get(text, text or "未分类")
238+
239+
199240
def normalize_ai_mapping(mapping: Any) -> dict[str, Any]:
200241
if not isinstance(mapping, dict):
201242
return {}
@@ -263,7 +304,7 @@ def strategy_style(item: WatchlistItem | None, events: list[Event], ai_bias: str
263304

264305
def horizon_fit(style: str, rating: str) -> tuple[str, list[str], str, str]:
265306
if rating in {"verify_source", "defer", "monitor"}:
266-
return "not_applicable", ["not_applicable"], "不适用", "该项不是当前推荐,仅用于来源核验、风险暂缓或持续监控。"
307+
return "not_applicable", ["not_applicable"], "不适用", "该项不是当前推荐,仅用于来源核验、风险暂缓或背景跟踪。"
267308
if style == "event_driven":
268309
return "medium", ["short", "medium"], "中线", "事件驱动主周期为2-12周;1-10个交易日只适合观察催化反应,波动和反转风险更高。"
269310
if style in {"long_horizon_growth", "value_quality"}:
@@ -279,7 +320,7 @@ def rating_label(rating: str) -> str:
279320
"watch": "观察",
280321
"verify_source": "先核验来源",
281322
"defer": "暂缓",
282-
"monitor": "监控",
323+
"monitor": "背景跟踪",
283324
}
284325
return labels[rating]
285326

@@ -310,14 +351,14 @@ def recommendation_tier(rating: str, score: float, confidence: str) -> tuple[str
310351
return "source_check", "来源核验"
311352
if rating == "defer":
312353
return "defer", "暂缓"
313-
return "monitor", "监控"
354+
return "monitor", "背景跟踪"
314355

315356

316357
def build_theme_first_candidates(
317358
theme_momentum: dict[str, Any] | None,
318359
recommendations: list[dict[str, Any]],
319360
*,
320-
max_candidates: int = 10,
361+
max_candidates: int = 8,
321362
max_themes: int = 5,
322363
) -> list[dict[str, Any]]:
323364
if not theme_momentum:
@@ -331,12 +372,15 @@ def build_theme_first_candidates(
331372
theme_rank = int(as_float(theme.get("rank"), default=999))
332373
theme_id = str(theme.get("theme_id", ""))
333374
theme_name = str(theme.get("theme_name", ""))
375+
theme_sector = str(theme.get("sector", ""))
334376
theme_score = round(as_float(theme.get("momentum_score")), 6)
335377
theme_breadth = round(as_float(theme.get("breadth_3m")), 6)
336378
for item in theme.get("top_symbols", []):
337379
if not isinstance(item, dict) or not item.get("symbol"):
338380
continue
339381
symbol = str(item["symbol"]).upper()
382+
if symbol in EXCLUDED_THEME_PICK_SYMBOLS:
383+
continue
340384
symbol_score = round(as_float(item.get("momentum_score")), 6)
341385
candidate = by_symbol.setdefault(
342386
symbol,
@@ -351,6 +395,7 @@ def build_theme_first_candidates(
351395
"best_theme_rank": theme_rank,
352396
"primary_theme_id": theme_id,
353397
"primary_theme_name": theme_name,
398+
"primary_theme_sector": theme_sector,
354399
"primary_theme_score": theme_score,
355400
"primary_theme_breadth_3m": theme_breadth,
356401
"theme_ids": [],
@@ -366,6 +411,7 @@ def build_theme_first_candidates(
366411
candidate["best_theme_rank"] = theme_rank
367412
candidate["primary_theme_id"] = theme_id
368413
candidate["primary_theme_name"] = theme_name
414+
candidate["primary_theme_sector"] = theme_sector
369415
candidate["primary_theme_score"] = theme_score
370416
candidate["primary_theme_breadth_3m"] = theme_breadth
371417
if theme_id and theme_id not in candidate["theme_ids"]:
@@ -374,6 +420,7 @@ def build_theme_first_candidates(
374420
{
375421
"theme_id": theme_id,
376422
"theme_name": theme_name,
423+
"sector": theme_sector,
377424
"rank": theme_rank,
378425
"momentum_score": theme_score,
379426
"breadth_3m": theme_breadth,
@@ -402,7 +449,7 @@ def build_theme_first_candidates(
402449
source_confirmation = f"已有{rec.get('source_confidence_label', '')}置信事件确认"
403450
theme_ids = candidate.get("theme_ids", [])
404451
reasons = [
405-
"主题动量排序靠前,适合放入本期主题优先候选池。",
452+
"主题和个股动量靠前,适合放入本期主题优先候选池。",
406453
(
407454
f"主主题 #{candidate.get('best_theme_rank')} {candidate.get('primary_theme_id')} "
408455
f"主题分数={candidate.get('primary_theme_score')},3个月广度={candidate.get('primary_theme_breadth_3m')}。"
@@ -411,9 +458,23 @@ def build_theme_first_candidates(
411458
]
412459
if len(theme_ids) > 1:
413460
reasons.append(f"同时暴露于多个强主题:{', '.join(theme_ids)}。")
461+
industry_background = (
462+
f"{sector_label(candidate.get('primary_theme_sector'))} / "
463+
f"{candidate.get('primary_theme_name') or candidate.get('primary_theme_id')}"
464+
)
465+
recommendation_summary = (
466+
f"属于{industry_background},主主题排名 #{candidate.get('best_theme_rank')};"
467+
f"个股动量 {display_number(candidate.get('symbol_momentum_score'))},"
468+
f"近3个月 {display_percent(candidate.get('return_3m'))}。"
469+
)
470+
if len(theme_ids) > 1:
471+
recommendation_summary += f" 同时覆盖 {', '.join(theme_ids[:3])} 等主题。"
472+
risk_summary = "需复核估值、财报、回撤、流动性和稳定事件证据。"
473+
if source_confirmation != "待事件确认":
474+
risk_summary = "已有事件确认,但仍需复核估值、财报、回撤和流动性。"
414475
risk_notes = [
415476
"该候选来自主题和价格动量排序,不代表个性化建议或下单信号。",
416-
"升级为正式推荐前,需要继续复核估值、财报、回撤、流动性和稳定事件证据。",
477+
risk_summary,
417478
]
418479
if rec and rec.get("risk_notes"):
419480
risk_notes.append(str(rec["risk_notes"][0]))
@@ -423,6 +484,9 @@ def build_theme_first_candidates(
423484
"rank": index,
424485
"advisor_status": advisor_status,
425486
"source_confirmation": source_confirmation,
487+
"industry_background": industry_background,
488+
"recommendation_summary": recommendation_summary,
489+
"risk_summary": risk_summary,
426490
"reasons": dedupe(reasons),
427491
"risk_notes": dedupe(risk_notes),
428492
}
@@ -691,25 +755,24 @@ def render_markdown(report: dict[str, Any]) -> str:
691755
]
692756
theme_candidates = report.get("theme_first_candidates", [])
693757
if theme_candidates:
694-
lines.extend(["## 主题优先候选", ""])
695-
lines.append("- 说明: 该列表按主题/个股动量排序,用于突出 AI 和高科技等强主题候选;不是下单或仓位建议。")
758+
lines.extend(["## 本期重点股票池", ""])
759+
lines.append("- 先看这里: 每期选出 5-10 个股票/公司标的,说明行业主题、入选理由、事件确认和主要风险。")
760+
lines.append("- 边界: 这是非个性化模型股票池,不是买入清单;`待事件确认` 表示稳定事件证据还不足。")
696761
lines.append("")
697762
for candidate in theme_candidates:
698763
lines.extend(
699764
[
700765
f"### #{candidate.get('rank')} {candidate.get('symbol')} - {candidate.get('advisor_status')}",
701766
"",
702-
f"- 主主题: `{candidate.get('primary_theme_id')}` / {candidate.get('primary_theme_name')}",
703-
f"- 个股动量分数: `{candidate.get('symbol_momentum_score')}`",
704-
f"- 3个月收益: `{candidate.get('return_3m', '')}`",
767+
f"- 行业/主题: {candidate.get('industry_background')}",
768+
f"- 主主题: `{candidate.get('primary_theme_id')}`",
769+
f"- 个股动量分数: `{display_number(candidate.get('symbol_momentum_score'))}`",
770+
f"- 近3个月: `{display_percent(candidate.get('return_3m'))}`",
705771
f"- 来源确认: `{candidate.get('source_confirmation')}`",
706-
f"- 相关主题: `{', '.join(candidate.get('theme_ids', []))}`",
707-
"- 理由:",
772+
f"- 为什么入选: {candidate.get('recommendation_summary')}",
773+
f"- 主要风险: {candidate.get('risk_summary')}",
708774
]
709775
)
710-
lines.extend(f" - {reason}" for reason in candidate.get("reasons", []))
711-
lines.append("- 风险:")
712-
lines.extend(f" - {risk}" for risk in candidate.get("risk_notes", []))
713776
lines.append("")
714777
theme_momentum = report.get("theme_momentum", {})
715778
if theme_momentum.get("available"):

0 commit comments

Comments
 (0)