From f046e00dff100e7510089e4e568f2165ca8a199e Mon Sep 17 00:00:00 2001
From: Cai <cai@nbcai.cc>
Date: Thu, 20 Aug 2026 18:57:31 +0800
Subject: [PATCH] fix(valuation): use three-zone semiconductor ranking
---
ai-valuation-analyst/tools/rank_semiconductor_valuations.py | 40 ++++++++++++++++++++++++++++++----------
1 files changed, 30 insertions(+), 10 deletions(-)
diff --git a/ai-valuation-analyst/tools/rank_semiconductor_valuations.py b/ai-valuation-analyst/tools/rank_semiconductor_valuations.py
index 58ee494..b66fe95 100644
--- a/ai-valuation-analyst/tools/rank_semiconductor_valuations.py
+++ b/ai-valuation-analyst/tools/rank_semiconductor_valuations.py
@@ -24,7 +24,7 @@
"所属行业",
"收盘价",
"基准区间",
- "低于基准下沿",
+ "估值位置偏离",
"原判定",
"泡沫判定",
"高于基准上沿",
@@ -56,12 +56,27 @@
return "—" if number is None else f"{number:+.2f}%"
-def below_base_low_pct(row: dict[str, str]) -> float | None:
+def valuation_position(row: dict[str, str]) -> tuple[int, float, str] | None:
close = as_float(row.get("close", ""))
low = as_float(row.get("base_low", ""))
- if close is None or low is None or low <= 0:
+ high = as_float(row.get("base_high", ""))
+ if close is None or low is None or high is None or low <= 0 or high <= low:
return None
- return (low - close) / low * 100.0
+ if close < low:
+ discount = (low - close) / low * 100.0
+ return 0, discount, f"低于下沿 {discount:.2f}%"
+ if close <= high:
+ midpoint = (low + high) / 2.0
+ deviation = (close - midpoint) / midpoint * 100.0
+ if deviation < -0.005:
+ display = f"低于中枢 {abs(deviation):.2f}%"
+ elif deviation > 0.005:
+ display = f"高于中枢 {deviation:.2f}%"
+ else:
+ display = "位于中枢 0.00%"
+ return 1, deviation, display
+ premium = (close - high) / high * 100.0
+ return 2, premium, f"高于上沿 {premium:.2f}%"
def display_industries(value: str) -> str:
@@ -129,7 +144,7 @@
"所属行业": display_industries(source["industries"]),
"收盘价": "—",
"基准区间": "—",
- "低于基准下沿": "—",
+ "估值位置偏离": "—",
"原判定": "特殊结论",
"泡沫判定": "不适用",
"高于基准上沿": "—",
@@ -147,14 +162,14 @@
low = as_float(current["base_low"])
high = as_float(current["base_high"])
premium = as_float(current["bubble_premium_pct"])
- discount = below_base_low_pct(current)
+ position = valuation_position(current)
output = {
"代码": source["ticker"],
"公司": source["company"],
"所属行业": display_industries(source["industries"]),
"收盘价": "—" if close is None else f"{close:.2f}",
"基准区间": "—" if low is None or high is None else f"{low:.2f}—{high:.2f}",
- "低于基准下沿": "—" if discount is None else f"{discount:.2f}%",
+ "估值位置偏离": "—" if position is None else position[2],
"原判定": current["label"],
"泡沫判定": current["bubble_status"],
"高于基准上沿": "—" if premium is None else f"{max(0.0, premium):.2f}%",
@@ -165,7 +180,11 @@
"相对MA60": signed_pct(current["distance_to_ma60_pct"]),
"正式报告": report_path,
}
- key = (0, float("inf") if discount is None else -discount, source["ticker"])
+ if position is None:
+ key = (3, float("inf"), source["ticker"])
+ else:
+ zone, metric, _ = position
+ key = (zone, -metric if zone == 0 else metric, source["ticker"])
ranked.append((key, output))
ranked.sort(key=lambda item: item[0])
@@ -180,9 +199,10 @@
"# 半导体股票估值排序",
"",
f"- 价格日:{'、'.join(trade_dates)};半导体研究证券共{len(rows)}只。",
- "- 排序:按 `低于基准下沿 = (基准下沿-收盘价)/基准下沿` 从高到低排列,数值越大表示相对基准下沿越便宜;无法计算的特殊结论置于末尾。",
+ "- 排序:低于下沿 → 基准区间内 → 高于上沿 → 特殊结论。低于下沿按折价从大到小;区间内按相对中枢偏离从低到高;高于上沿按溢价从小到大。",
f"- 分布:偏低{label_counts['偏低']}只、基本合理{label_counts['基本合理']}只、偏贵{label_counts['偏贵']}只、明显偏贵{label_counts['明显偏贵']}只、特殊结论{label_counts['特殊结论']}只。",
- "- “低于基准下沿”为正表示收盘价低于下沿,为负表示收盘价高于下沿;该字段是价格位置比较,不代表经营风险已经消除。",
+ "- “估值位置偏离”分段计算:低于下沿用 `(下沿-收盘价)/下沿`;区间内用 `(收盘价-区间中枢)/区间中枢`;高于上沿用 `(收盘价-上沿)/上沿`。",
+ "- 该排序只比较当前价格在既定合理区间中的位置,不代表经营风险已经消除,也不把不同公司的估值区间置信度视为完全相同。",
"- “高于基准上沿”为0表示没有超过基准合理区间上沿;泡沫原因除价格位置外属于最可能解释,不是已经证实的资金因果。",
f"- 输入:`全部行业调研标的估值覆盖清单.csv` SHA-256 `{sha256(COVERAGE_PATH)}`;`../估值台账/latest.csv` SHA-256 `{sha256(LATEST_PATH)}`。",
"- 合理区间是条件化估值,不是目标价、交易指令或收益承诺。",
--
Gitblit v1.9.3