Files
tick-stock-panel/backend/app/api/rps.py
T
shy3130 c9f01ba288 feat: 行业轮动分析(参数化复用概念版) + 扩展数据列数字格式化
行业轮动分析(完全对齐概念版)
- 概念轮动逻辑参数化支持 industry 维度, 一套代码服务两个页面(复用 _dimension_field)
- 后端 build_rps_rotation/analyze_rotation_stream 加 kind 参数; 缓存按 kind+level 隔离
- 行业层级: 1/2/3 级可切换, 默认 2 级(与 _dimension_rank 口径一致)
  · 原始 257 个 3 级行业 → level=2 合并成 90 个
- AI 分析 prompt 动态化(概念/行业措辞), _SYSTEM_PROMPT 改为 _build_system_prompt(kind)
- 前端 RpsRotationDialog 加 kind prop; industry 显示层级选择器(1/2/3级默认2)
- IndustryAnalysis 加「涨幅RPS轮动分析」按钮 + dialog(kind=industry)
- ConceptAnalysis 不动(默认 kind=concept 向后兼容)

扩展数据列数字格式化(自选/策略列表)
- 列配置新增 3 项(仅数字类型字段): 千分位逗号 / 单位换算(万/亿/自动) / 小数位
- 新增 formatExtNumber 共享格式化函数(千分位+换算+小数位, 去尾零)
- Watchlist + ScreenerTable 的 number 渲染接 formatExtNumber
- ext source 加 fieldType; 旧列无 fieldType 时默认显示数字配置(放宽判断)
- ExtColumnDisplayConfig 加 thousandSeparator/unitConvert/unitDecimals 字段

验证
- 后端 583 passed; 行业映射实测 257→90(level=2)
- 数字格式化实测: 千分位 1,234,567 / 万换算 123.46万 / 自动 等均正确
- tsc + pnpm build 通过
2026-08-02 19:18:07 +08:00

74 lines
2.7 KiB
Python

"""涨幅轮动矩阵 API。
供「概念分析 → 涨幅RPS轮动」对话框调用。返回最近 N 个交易日的概念涨幅
排名矩阵:每列(日期)各自把所有概念按当天涨幅从高到低排序。
"""
from __future__ import annotations
from fastapi import APIRouter, Query, Request
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from app.services import rps_rotation
from app.services.concept_rotation_analyzer import analyze_rotation_stream
router = APIRouter(prefix="/api/rps", tags=["rps"])
@router.get("/rotation")
def get_rotation(
request: Request,
days: int = Query(12, ge=7, le=30, description="最近 N 个交易日(7-30)"),
kind: str = Query("concept", pattern="concept|industry", description="维度: concept 概念 / industry 行业"),
level: int | None = Query(None, ge=1, le=3, description="行业层级(仅 kind=industry): 1/2/3 级"),
) -> dict:
"""维度涨幅轮动矩阵(概念或行业)。
Returns:
dates: 日期字符串列表(最新在最前)
columns: {日期: [[成员名, 涨幅小数], ...]} 每列各自降序
concept_count: 去重维度成员总数
"""
return rps_rotation.build_rps_rotation(request.app.state.repo, days, kind, level)
class AnalyzeRequest(BaseModel):
"""AI 维度轮动分析请求(概念或行业)。"""
days: int = 12 # 分析最近 N 个交易日
focus: str = "" # 用户追加的关注点
kind: str = "concept" # "concept" 概念 / "industry" 行业
level: int | None = None # 行业层级(1/2/3), 仅 kind=industry 有效
@router.post("/rotation-analyze")
async def analyze_rotation(request: Request, req: AnalyzeRequest):
"""AI 维度轮动分析 — NDJSON 流式返回。
装配轮动矩阵信号 + 大盘背景 → 分析提示词 → 流式调用 LLM →
逐 chunk 以 NDJSON 推给前端(每行一个 JSON)。
协议:
{"type":"meta","days","summary"}
{"type":"delta","content":"..."}
{"type":"error","message":"..."}
{"type":"done"}
"""
repo = request.app.state.repo
quote_service = getattr(request.app.state, "quote_service", None)
depth_service = getattr(request.app.state, "depth_service", None)
days = max(7, min(30, req.days))
kind = "industry" if req.kind == "industry" else "concept"
level = req.level if (kind == "industry" and req.level in (1, 2, 3)) else None
async def stream_gen():
async for chunk in analyze_rotation_stream(
repo, days, req.focus, quote_service, depth_service, kind, level,
):
yield chunk + "\n"
return StreamingResponse(
stream_gen(),
media_type="application/x-ndjson",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)