fix(rotation): AI 轮动分析的大盘背景不再把指数涨跌幅放大 100 倍

「概念分析/行业分析 → AI 轮动分析」生成的报告里, 大盘部分写成「上证指数
上涨 123%」「深证成指下跌 45%」, 第 5 节「结合大盘」的结论也跟着跑偏。

_build_market_block 用 _fmt_pct 渲染指数涨跌幅, 而 _fmt_pct 是给概念/行业
涨幅(小数口径)写的, 会 *100。但 overview["indices"][].change_pct 是百分数
口径 (CONTRIBUTING §3.1): quote_service._build_index_quotes 的注释写明
"统一转成百分比输出", DB 兜底的 _index_quotes 同样 change_amount/pc*100。
于是 1.23 被渲染成 +123.00% 送进提示词。

新增 _fmt_index_pct 按百分数口径直接格式化, 与 market_recap._build_indices_block
(同一份 overview, 不 *100) 和 abnormal_moves._bench_rt_pct (消费前显式 /100)
的口径对齐; 概念/行业涨幅仍走原来的 _fmt_pct。
This commit is contained in:
kevin9327
2026-09-10 08:04:48 +09:00
parent d0a14b5c1b
commit a714d70f83
2 changed files with 70 additions and 1 deletions
@@ -206,11 +206,24 @@ def _compute_rotation_signals(dates: list[str], columns: dict) -> dict:
# ================================================================
def _fmt_pct(v) -> str:
"""概念/行业涨幅: 小数口径 (0.0522 = +5.22%), 展示前乘 100。"""
if v is None:
return ""
return f"{v*100:+.2f}%"
def _fmt_index_pct(v) -> str:
"""指数涨跌幅: 百分数口径 (CONTRIBUTING §3.1), 直接展示, 不能再乘一次 100。
build_market_overview 的 indices[].change_pct 在数据边界已转成百分数
(quote_service._build_index_quotes 与 _index_quotes 的 DB 兜底都已乘过 100),
与 market_recap._build_indices_block 的展示口径一致。
"""
if v is None:
return ""
return f"{v:+.2f}%"
def _build_market_block(overview: dict) -> str:
"""大盘背景精简块 (复用 market_overview 已算好的字段)。"""
indices = overview.get("indices") or []
@@ -222,7 +235,7 @@ def _build_market_block(overview: dict) -> str:
for idx in indices[:4]:
name = idx.get("name") or idx.get("symbol") or "?"
chg = idx.get("change_pct")
idx_lines.append(f"{name} {_fmt_pct(chg)}")
idx_lines.append(f"{name} {_fmt_index_pct(chg)}")
idx_str = " / ".join(idx_lines) or "指数缺失"
total_amount = (amt.get("total") or 0) / 1e8 # 元 → 亿
@@ -0,0 +1,56 @@
"""AI 轮动分析提示词里的「大盘背景」指数涨跌幅必须按百分数口径。
CONTRIBUTING §3.1: build_market_overview 的 indices[].change_pct 是百分数
(quote_service._build_index_quotes 在数据边界已乘过 100, DB 兜底路径同样乘过 100)。
概念/行业涨幅则是小数。两者共用一个 _fmt_pct 会把指数再放大 100 倍,
喂给 LLM 的大盘背景变成「上证指数 +123.00%」。
"""
from __future__ import annotations
from app.services.concept_rotation_analyzer import (
_build_market_block,
_build_signal_block,
)
def _overview() -> dict:
"""与 build_market_overview 返回结构一致的最小切片。"""
return {
"indices": [
{"symbol": "000001.SH", "name": "上证指数", "change_pct": 1.23},
{"symbol": "399001.SZ", "name": "深证成指", "change_pct": -0.45},
],
"emotion": {"score": 62, "label": "偏暖"},
"limit": {"limit_up": 68, "broken": 20, "limit_down": 3, "max_boards": 5},
"amount": {"total": 1.2e12},
}
def test_index_change_pct_is_not_scaled_again():
block = _build_market_block(_overview())
assert "上证指数 +1.23%" in block
assert "深证成指 -0.45%" in block
assert "123.00%" not in block
def test_missing_index_change_pct_renders_placeholder():
overview = _overview()
overview["indices"][0]["change_pct"] = None
assert "上证指数 —" in _build_market_block(overview)
def test_dimension_pct_is_still_scaled_from_decimal():
"""概念/行业涨幅仍是小数口径, 必须乘 100 后展示 (不能被一起改掉)。"""
items = [{
"concept": "人工智能",
"ranks": [3, 1],
"pcts": [0.05, 0.07],
"avg_rank": 2.0,
"rank_std": 1.0,
}]
block = _build_signal_block("主线", items)
assert "区间均涨 +6.00%" in block