Files
stock_cursor_v0/20260613T161709_session.md
2026-06-14 11:54:45 +08:00

1321 lines
51 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
### [Continue](https://continue.dev) session transcript
Exported: 6/13/2026, 4:17:09 PM
#### _User_
> 你是什么模型 版本号多少
#### _Assistant_
> 我是 Claude由 Anthropic 开发,具体是 Claude Opus 4.6(模型 IDclaude-opus-4-6。有什么可以帮你的
#### _User_
> 帮我分析一下当前项目 有没有什么优化的功能
#### _Assistant_
> 我先来了解一下你的项目结构和内容。
>
>
#### _Assistant_
> .git/
> .gitignore
> backend/
> prototype/
> README.md
> 功能架构.md
> 架构总结.md
#### _Assistant_
> 让我先看看项目的架构文档和主要代码结构。
>
>
#### _Assistant_
> # 智策 StockTerminal
>
>
>
> 个人/小团队 A 股分析·复盘·智能辅助系统。后端提供行情、回测、AI 诊断与定时任务;前端为纯 HTML + ECharts 原型界面,由 FastAPI 统一托管。
>
>
>
> ## 功能概览
>
>
>
> | 模块 | 能力 |
>
> |---|---|
>
> | 大盘行情 | 三大指数、情绪温度计、板块云图、热股榜、龙虎榜 |
>
> | 自选股 | 自选列表、内置策略选股、多因子条件过滤 |
>
> | 复盘中心 | 每日复盘、AI 七段式日报、个股 K 线回放 |
>
> | 策略与中台 | MA 交叉回测、数据入库状态、任务日志 |
>
> | AI 分析 | 个股诊断6 维证据链)、信号胜率、预测留痕与核验 |
>
> | 组合交易 | 持仓 P&L、资金曲线、交易日志、盈亏归因 |
>
> | 智能预警 | 价格/涨跌幅规则、多通道推送、触发记录 |
>
> | 资讯中心 | 财经快讯、情绪标注、自选相关资讯 |
>
>
>
> 更完整的架构说明见 [架构总结.md](./架构总结.md)。
>
>
>
> ## 技术栈
>
>
>
> - **前端**HTML + CSS + 原生 JSECharts 5CDN
>
> - **后端**Python 3.12 · FastAPI · uvicorn
>
> - **数据库**PostgreSQL · SQLAlchemy 2.0
>
> - **数据源**AkShare行情/情绪/资讯Sina 实时报价
>
> - **调度**APScheduler
>
> - **AI**OpenAI 兼容接口DeepSeek / 通义 / Kimi 等),无 Key 时规则降级
>
>
>
> ## 项目结构
>
>
>
> ```
>
> stock_cs/
>
> ├── backend/ # FastAPI 后端
>
> │ ├── main.py # API 入口 + 静态文件托管
>
> │ ├── cli.py # 建库 / 入库命令行工具
>
> │ ├── .env.example # 环境变量模板
>
> │ └── requirements.txt
>
> ├── prototype/ # 前端原型index.html / app.js / style.css
>
> ├── 架构总结.md
>
> └── 功能架构.md
>
> ```
>
>
>
> ## 环境要求
>
>
>
> - Python 3.12+
>
> - PostgreSQL 14+(本地或远程均可)
>
> - 可选:大模型 API Key、推送渠道密钥见下方配置
>
>
>
> ## 快速开始
>
>
>
> ### 1. 安装依赖
>
>
>
> ```bash
>
> cd backend
>
> python -m venv .venv
>
>
>
> # Windows
>
> .venv\Scripts\activate
>
>
>
> # Linux / macOS
>
> source .venv/bin/activate
>
>
>
> pip install -r requirements.txt
>
> ```
>
>
>
> ### 2. 配置环境变量
>
>
>
> ```bash
>
> cp .env.example .env
>
> ```
>
>
>
> 编辑 `backend/.env`,至少确认 PostgreSQL 连接信息(也可通过环境变量 `PG_USER` / `PG_PASSWORD` / `PG_HOST` / `PG_PORT` / `PG_DB` 设置):
>
>
>
> ```env
>
> PG_USER=postgres
>
> PG_PASSWORD=your_password
>
> PG_HOST=localhost
>
> PG_PORT=5432
>
> PG_DB=stock_cs
>
> ```
>
>
>
> 可选:填入 `LLM_API_KEY` 启用大模型分析;填入 SMTP / Server酱 / 企业微信 / PushPlus 启用推送。
>
>
>
> ### 3. 初始化数据库并入库
>
>
>
> ```bash
>
> cd backend
>
>
>
> # 建库建表
>
> python cli.py init
>
>
>
> # 抓取当日板块/资金流/情绪/龙虎榜等快照
>
> python cli.py ingest
>
>
>
> # 全市场日线历史入库(默认 250 交易日,耗时较长)
>
> python cli.py ingest_all
>
> python cli.py ingest_all 500 # 指定天数
>
> ```
>
>
>
> 指定股票入库:
>
>
>
> ```bash
>
> python cli.py ingest 600519 000001
>
> ```
>
>
>
> ### 4. 启动服务
>
>
>
> ```bash
>
> cd backend
>
> python main.py
>
> ```
>
>
>
> 浏览器访问:**http://127.0.0.1:8000**
>
>
>
> 健康检查:`GET /api/health`
>
>
>
> ## 定时任务
>
>
>
> 服务启动后APScheduler 会在工作日自动执行(可在 `config.py` 或环境变量中调整时间):
>
>
>
> | 任务 | 默认时间 | 说明 |
>
> |---|---|---|
>
> | `daily_ingest` | 15:35 | 收盘后增量入库 |
>
> | `alert_check` | 每 60 秒 | 实时报价预警核查 |
>
> | `daily_report` | 15:45 | 生成 AI 复盘日报并推送 |
>
> | `verify_pred` | 15:50 | 核验到期 AI 预测 |
>
> | `signal_stats` | 周六 09:00 | 全市场信号胜率回测 |
>
>
>
> ## 推送渠道
>
>
>
> 在 `.env` 中配置任意一种即可启用,互不依赖:
>
>
>
> | 渠道 | 配置项 |
>
> |---|---|
>
> | SMTP 邮件 | `SMTP_HOST` / `SMTP_PORT` / `SMTP_USER` / `SMTP_PASSWORD` / `SMTP_TO` |
>
> | Server酱 | `SERVERCHAN_KEY` |
>
> | 企业微信 | `WECOM_WEBHOOK` |
>
> | PushPlus | `PUSHPLUS_TOKEN` |
>
>
>
> ## 开发说明
>
>
>
> - 前端静态资源由 `main.py` 挂载 `prototype/` 目录,修改前端后刷新浏览器即可。
>
> - 自选股列表持久化在 `backend/watchlist.json`。
>
> - AkShare 不可用时部分接口会降级为 mock 数据,详见 `/api/health` 中的 `akshare` 字段。
>
> - 敏感文件(`.env`、虚拟环境等)已在 `.gitignore` 中排除,请勿提交密钥。
>
>
>
> ## 文档
>
>
>
> - [架构总结.md](./架构总结.md) — 分层设计、数据模型、AI 分析流程
>
> - [功能架构.md](./功能架构.md) — 功能模块说明
>
>
>
> ## 许可证
>
>
>
> 本项目仅供学习与研究使用。行情数据来源于第三方公开接口,请遵守相应数据源的使用条款。
>
>
#### _Assistant_
> # 智策 StockTerminal — 架构总结
>
>
>
> > 版本当前实现态2026-06
>
> > 定位:个人/小团队 A 股分析·复盘·智能辅助系统
>
>
>
> ---
>
>
>
> ## 一、整体分层
>
>
>
> ```
>
> ┌─────────────────────────────────────────────────────────────┐
>
> │ 展示层(前端) │
>
> │ HTML + ECharts 5 + 原生 JS │
>
> │ prototype/index.html · style.css · app.js │
>
> └────────────────────────┬────────────────────────────────────┘
>
> │ HTTP / REST
>
> ┌────────────────────────▼────────────────────────────────────┐
>
> │ 服务层(后端 FastAPI
>
> │ main.py —— 50+ API 端点,静态文件托管 │
>
> │ │
>
> │ 数据服务 akshare_service.py (行情·情绪·资讯·实时报价) │
>
> │ AI 分析 ai.py (证据链·置信度·LLM/规则) │
>
> │ 信号统计 signals.py (胜率回测·预测留痕·核验) │
>
> │ RAG 检索 rag.py (资讯检索·情绪标注) │
>
> │ 复盘日报 report.py (结构化日报·推送) │
>
> │ 组合计算 portfolio.py (持仓·P&L·资金曲线) │
>
> │ 回测引擎 backtest.py (MA交叉·净值曲线) │
>
> │ 预警引擎 alerts.py (实时报价判断·触发事件) │
>
> │ 推送通知 notifier.py (SMTP·Server酱·企微·PP) │
>
> │ 大模型客户端 llm.py (OpenAI 兼容接口) │
>
> │ 定时调度 scheduler.py (APScheduler 5 个任务) │
>
> │ ETL 入库 ingest.py (AkShare→PostgreSQL) │
>
> │ CLI 工具 cli.py (init/ingest/ingest_all) │
>
> │ 配置 config.py + .env (DB·LLM·推送密钥) │
>
> └────────────────────────┬────────────────────────────────────┘
>
> │ SQLAlchemy ORM / psycopg2
>
> ┌────────────────────────▼────────────────────────────────────┐
>
> │ 数据层PostgreSQL
>
> │ 13 张业务表,见下方数据模型 │
>
> └─────────────────────────────────────────────────────────────┘
>
> ```
>
>
>
> ---
>
>
>
> ## 二、后端模块职责
>
>
>
> | 文件 | 职责 | 关键依赖 |
>
> |---|---|---|
>
> | `main.py` | FastAPI 入口50+ REST 端点,静态托管 | 所有模块 |
>
> | `config.py` | 环境变量读取,支持 `.env` 文件 | python-dotenv |
>
> | `db.py` | 引擎/Session自动建库建表 | SQLAlchemy, psycopg2 |
>
> | `models.py` | 13 个 ORM 表(见下节) | SQLAlchemy |
>
> | `akshare_service.py` | AkShare 数据抓取,带 TTL 缓存和 mock 降级 | akshare, cachetools |
>
> | `ingest.py` | ETLAkShare → PostgreSQL 增量 upsert | akshare, models |
>
> | `scheduler.py` | APScheduler 后台任务5 个定时任务) | apscheduler |
>
> | `backtest.py` | MA 交叉策略回测,读 DB 日线,输出净值曲线 | sqlalchemy, pandas |
>
> | `ai.py` | AI 分析证据链构造、置信度计算、LLM 调用/规则降级 | llm, signals, rag |
>
> | `signals.py` | 6 类信号历史胜率回测;预测留痕 + 到期核验 | sqlalchemy, pandas, numpy |
>
> | `rag.py` | 资讯检索 + 利好/利空情绪标注,作为 LLM 上下文 | akshare_service |
>
> | `report.py` | 七段式 AI 复盘日报生成、落库、推送精简版 | ai, notifier |
>
> | `portfolio.py` | 持仓计算移动加权均价、P&L 归因、逐日资金曲线 | sqlalchemy |
>
> | `alerts.py` | 实时报价轮询 → 规则命中 → 写事件 + 推送 | akshare_service, notifier |
>
> | `notifier.py` | 四渠道推送SMTP 邮件、Server酱、企业微信 Webhook、PushPlus | requests, smtplib |
>
> | `llm.py` | OpenAI 兼容客户端DeepSeek/通义/Kimi 均适用) | requests |
>
> | `cli.py` | 命令行工具:`init` / `ingest` / `ingest_all` | ingest, db |
>
>
>
> ---
>
>
>
> ## 三、数据模型PostgreSQL
>
>
>
> | 表名 | 说明 | 主键 / 唯一约束 |
>
> |---|---|---|
>
> | `securities` | 证券基础信息(代码·名称·市场) | `code` |
>
> | `quotes_daily` | 个股日线OHLCV前复权 | `(code, date)` |
>
> | `index_daily` | 指数日线(上证/深证/沪深300 | `(code, date)` |
>
> | `sector_daily` | 板块每日快照(涨跌·成交额·龙头) | `(date, name)` |
>
> | `fund_flow_daily` | 行业主力资金流每日快照 | `(date, name)` |
>
> | `sentiment_daily` | 全市场情绪(涨跌家数/涨跌停) | `date` |
>
> | `dragon_tiger` | 龙虎榜明细(代码·席位·净买额) | `(date, code, reason)` |
>
> | `stock_metrics` | 个股最新因子快照MA/RSI/MACD/量比/分位/连涨 等 15 个因子) | `code` |
>
> | `signal_stats` | 6 类技术信号历史胜率回测样本·N日上涨概率·平均收益 | `(signal, horizon)` |
>
> | `predictions` | AI 诊断留痕方向·置信度·N日后核验·命中与否 | `(code, date, kind)` |
>
> | `daily_reports` | AI 复盘日报markdown 正文·来源·是否已推送) | `date` |
>
> | `trades` | 交易记录(买卖·价格·数量·手续费·理由·情绪标签) | `id` |
>
> | `alert_rules` | 预警规则(价格上穿/下穿/涨跌幅条件·状态) | `id` |
>
> | `alert_events` | 预警触发事件(站内通知·已读状态) | `id` |
>
> | `job_runs` | 定时任务执行日志 | `id` |
>
>
>
> ---
>
>
>
> ## 四、定时任务APScheduler周一至周五
>
>
>
> | 任务 ID | 触发时间 | 功能 |
>
> |---|---|---|
>
> | `daily_ingest` | 15:35收盘后可配 | 抓取当日板块/资金流/情绪/龙虎榜/自选股日线入库 |
>
> | `alert_check` | 每 60 秒 | 实时报价核查所有 active 预警规则,触发则写事件并推送 |
>
> | `daily_report` | 15:45入库+10分 | 生成 AI 七段式复盘日报并推送微信/邮件 |
>
> | `verify_pred` | 15:50 | 核验到期 AI 预测,计算命中率 |
>
> | `signal_stats` | 每周六 09:00 | 对全市场样本股回测 6 类信号历史胜率(可手动触发) |
>
>
>
> ---
>
>
>
> ## 五、AI 分析层设计(可回溯)
>
>
>
> ```
>
> 用户请求 /api/ai/diagnose
>
>
>
>
>
> _stock_context() 从 stock_metrics + DailyQuote 取最新因子
>
>
>
> ├─ signals.get_stats() 读 signal_stats 表,取各信号历史胜率
>
>
>
> ├─ rag.stock_context() 拉近期资讯 → 情绪标注(利好/利空/中性)
>
>
>
>
>
> _build_evidence() 生成 6 维证据链(趋势/技术/动量/资金/位置/消息)
>
> 每条附:事实描述 · 方向(bull/bear/neutral) · 历史胜率 · 样本数
>
>
>
>
>
> _confidence_direction() 加权多空净值 → 置信度(%) + 预测方向(up/down/flat)
>
>
>
> ├─ signals.record_prediction() 写入 predictions 表留痕
>
>
>
> ├─ llm.ask() (有 key) 将证据链+胜率+RAG资讯构造 prompt → 大模型输出
>
> │ 或
>
> └─ 规则文本降级 格式化证据链为可读文本
>
> ```
>
>
>
> 实测准确率闭环:
>
> ```
>
> 预测留痕(open) → N 个交易日后 verify_predictions() 拉 DailyQuote 核验
>
> → 填写 actual_ret + hit(True/False) → status=closed
>
> → /api/ai/accuracy 按方向汇总命中率
>
> ```
>
>
>
> ---
>
>
>
> ## 六、推送通知渠道
>
>
>
> | 渠道 | 触发条件 | 配置项(`.env` |
>
> |---|---|---|
>
> | SMTP 邮件 | 预警触发 / 复盘日报 / 测试 | `SMTP_HOST/PORT/USER/PASSWORD/TO` |
>
> | Server酱微信 | 同上 | `SERVERCHAN_KEY` |
>
> | 企业微信群机器人 | 同上 | `WECOM_WEBHOOK` |
>
> | PushPlus微信 | 同上 | `PUSHPLUS_TOKEN` |
>
>
>
> 任意渠道配置即自动启用,互不依赖。
>
>
>
> ---
>
>
>
> ## 七、前端菜单结构app.js MENU
>
>
>
> ```
>
> 大盘行情
>
> ├─ 市场总览 行情/三大指数/情绪温度计/资金流向
>
> ├─ 大盘云图 行业/板块涨跌热力图ECharts treemap
>
> ├─ 热股榜 实时热股+板块
>
> └─ 龙虎榜 当日龙虎榜净买额排名
>
>
>
> 自选股
>
> ├─ 自选列表 持仓快照 + K线
>
> ├─ 全市场选股 8 个内置策略 + SQL 直查 stock_metrics
>
> └─ 条件选股 客户端多因子过滤
>
>
>
> 复盘中心
>
> ├─ 每日复盘 板块/情绪/资金流统计
>
> ├─ AI日报 七段式 markdown 日报,历史翻阅,一键推送
>
> └─ 个股复盘 K线回放 + MA交叉买卖点标注
>
>
>
> 策略与中台
>
> ├─ 策略回测 MA交叉净值曲线 + 最大回撤
>
> └─ 数据中台 入库状态 / 手动触发 / 任务日志
>
>
>
> AI 分析
>
> ├─ 今日策略 大模型/规则 生成当日操作方向
>
> ├─ 个股诊断 6维证据链 + 历史胜率 + 置信度 + RAG资讯
>
> ├─ AI复盘点评 当日大模型复盘摘要
>
> └─ AI准确率 信号历史胜率表 + 实测命中率 + 近期核验记录
>
>
>
> 组合交易
>
> ├─ 组合持仓 实时市值/浮亏/胜率
>
> ├─ 资金曲线 逐日净值 vs 沪深300基准ECharts折线
>
> ├─ 交易日志 录入买卖 / 理由 / 情绪标签
>
> └─ 盈亏归因 按个股/理由/情绪三维归因
>
>
>
> 智能预警
>
> ├─ 预警规则 创建/删除/重激活规则,推送渠道状态检测
>
> └─ 触发记录 预警事件列表,标记已读,🔔铃铛角标
>
>
>
> 资讯中心
>
> ├─ 要闻快讯 全球财经资讯 + 情绪标注 + AI摘要
>
> └─ 自选相关 自选股关联资讯聚合
>
> ```
>
>
>
> ---
>
>
>
> ## 八、技术栈
>
>
>
> | 层 | 技术选型 |
>
> |---|---|
>
> | 前端 | 纯 HTML + CSS + 原生 JSECharts 5CDN |
>
> | 后端 | Python 3.12 + FastAPI 0.115 + uvicorn |
>
> | 数据库 | PostgreSQLpsycopg2-binary + SQLAlchemy 2.0 |
>
> | 数据源 | AkShare新浪/同花顺/乐估备选源Sina hq 实时报价 |
>
> | 调度 | APScheduler 3.x BackgroundScheduler |
>
> | AI | OpenAI 兼容 REST默认 DeepSeek规则降级无缝切换 |
>
> | 缓存 | cachetools TTL内存无 Redis 依赖 |
>
> | 推送 | SMTP(SSL) + Server酱 + 企业微信 Webhook + PushPlus |
>
> | 部署 | WSL2 / Linux`nohup python main.py`,可 Docker 化 |
>
>
>
> ---
>
>
>
> ## 九、待完善方向
>
>
>
> | 优先级 | 模块 | 建议 |
>
> |---|---|---|
>
> | 高 | 回测引擎 | 手续费/滑点建模,参数寻优,月度收益热力图 |
>
> | 高 | 数据稳定性 | 东财源封锁问题,增加 Tushare 备用源,分钟线入库 |
>
> | 中 | RAG 升级 | Embedding + 向量库Chroma/Qdrant支持语义检索财报 |
>
> | 中 | 多组合 | 目前单一组合,扩展为模拟盘/真实盘多组合管理 |
>
> | 中 | 盘中异动雷达 | 快速拉升/放量突破/涨停打开实时监测 |
>
> | 低 | 用户系统 | 登录/鉴权,多用户自选股独立管理 |
>
> | 低 | 部署文档 | Docker ComposeFastAPI + PostgreSQLNginx 反代 |
>
>
#### _Assistant_
> .env.example
> .venv/
> ai.py
> akshare_service.py
> alerts.py
> backtest.py
> cli.py
> config.py
> db.py
> ingest.py
> llm.py
> main.py
> models.py
> notifier.py
> portfolio.py
> rag.py
> report.py
> requirements.txt
> scheduler.py
> signals.py
> __pycache__/
#### _Assistant_
> """智策股票终端 — FastAPI 后端入口。
>
>
>
> - /api/* : 数据接口(基于 AkShare带缓存与降级
>
> - / : 托管前端原型prototype 目录)
>
> """
>
> import os
>
> import json
>
> import datetime as dt
>
> from contextlib import asynccontextmanager
>
>
>
> from fastapi import FastAPI, Query
>
> from fastapi.middleware.cors import CORSMiddleware
>
> from fastapi.staticfiles import StaticFiles
>
> from sqlalchemy import select, func, desc
>
>
>
> from pydantic import BaseModel
>
>
>
> import akshare_service as svc
>
> import config
>
> import scheduler
>
> import backtest as bt
>
> import ai
>
> import signals as sig
>
> import report as rpt
>
> import portfolio as pf
>
> import llm
>
> import alerts as al
>
> import notifier
>
> from db import init_db, get_session
>
> from models import (DailyQuote, IndexDaily, SectorDaily, FundFlowDaily,
>
> SentimentDaily, DragonTiger, Security, JobRun, StockMetric, Trade,
>
> AlertRule, AlertEvent)
>
>
>
>
>
> @asynccontextmanager
>
> async def lifespan(app: FastAPI):
>
> try:
>
> init_db()
>
> scheduler.start_scheduler()
>
> print("[startup] db + scheduler ready")
>
> except Exception as e:
>
> print("[startup] WARN:", repr(e)[:160])
>
> yield
>
>
>
>
>
> app = FastAPI(title="智策股票终端 API", version="0.2.0", lifespan=lifespan)
>
>
>
> app.add_middleware(
>
> CORSMiddleware,
>
> allow_origins=["*"],
>
> allow_methods=["*"],
>
> allow_headers=["*"],
>
> )
>
>
>
> # 自选股本地存储
>
> BASE_DIR = os.path.dirname(os.path.abspath(__file__))
>
> WATCH_FILE = os.path.join(BASE_DIR, "watchlist.json")
>
> DEFAULT_WATCH = ["600519", "300750", "002594", "688981", "300059", "601012"]
>
>
>
>
>
> def load_watch():
>
> if os.path.exists(WATCH_FILE):
>
> try:
>
> with open(WATCH_FILE, "r", encoding="utf-8") as f:
>
> return json.load(f)
>
> except Exception:
>
> pass
>
> return DEFAULT_WATCH
>
>
>
>
>
> def save_watch(symbols):
>
> with open(WATCH_FILE, "w", encoding="utf-8") as f:
>
> json.dump(symbols, f, ensure_ascii=False)
>
>
>
>
>
> # ============ API ============
>
> @app.get("/api/health")
>
> def health():
>
> return {"ok": True, "akshare": svc.AK_OK}
>
>
>
>
>
> @app.get("/api/indices")
>
> def indices():
>
> return svc.get_indices()
>
>
>
>
>
> @app.get("/api/kline")
>
> def kline(symbol: str = Query("600519"), days: int = Query(120, ge=20, le=500)):
>
> return svc.get_kline(symbol, days)
>
>
>
>
>
> @app.get("/api/sentiment")
>
> def sentiment():
>
> return svc.get_sentiment()
>
>
>
>
>
> @app.get("/api/treemap")
>
> def treemap(mode: str = Query("sector")):
>
> return svc.get_treemap(mode)
>
>
>
>
>
> @app.get("/api/fundflow")
>
> def fundflow():
>
> return svc.get_fund_flow()
>
>
>
>
>
> @app.get("/api/hot/stocks")
>
> def hot_stocks():
>
> return svc.get_hot_stocks()
>
>
>
>
>
> @app.get("/api/hot/sectors")
>
> def hot_sectors():
>
> return svc.get_industry_boards()
>
>
>
>
>
> @app.get("/api/dragon")
>
> def dragon():
>
> return svc.get_dragon_tiger()
>
>
>
>
>
> @app.get("/api/watchlist")
>
> def watchlist():
>
> return svc.get_watchlist(load_watch())
>
>
>
>
>
> @app.post("/api/watchlist/{code}")
>
> def watch_add(code: str):
>
> w = load_watch()
>
> if code not in w:
>
> w.append(code)
>
> save_watch(w)
>
> return {"ok": True, "list": w}
>
>
>
>
>
> @app.delete("/api/watchlist/{code}")
>
> def watch_del(code: str):
>
> w = [c for c in load_watch() if c != code]
>
> save_watch(w)
>
> return {"ok": True, "list": w}
>
>
>
>
>
> # ============ 数据中台 ============
>
> @app.get("/api/admin/status")
>
> def admin_status():
>
> counts, last_dates = {}, {}
>
> with get_session() as s:
>
> for label, model in [("securities", Security), ("quotes_daily", DailyQuote),
>
> ("index_daily", IndexDaily), ("sector_daily", SectorDaily),
>
> ("fund_flow_daily", FundFlowDaily), ("sentiment_daily", SentimentDaily),
>
> ("dragon_tiger", DragonTiger)]:
>
> counts[label] = s.execute(select(func.count()).select_from(model)).scalar() or 0
>
> if hasattr(model, "date"):
>
> d = s.execute(select(func.max(model.date))).scalar()
>
> last_dates[label] = d.isoformat() if d else None
>
> jobs = s.execute(select(JobRun).order_by(desc(JobRun.id)).limit(8)).scalars().all()
>
> job_list = [{"id": j.id, "job": j.job, "status": j.status,
>
> "started": j.started_at.strftime("%m-%d %H:%M:%S") if j.started_at else "",
>
> "finished": j.finished_at.strftime("%H:%M:%S") if j.finished_at else "",
>
> "message": j.message[:200]} for j in jobs]
>
> return {"counts": counts, "last_dates": last_dates, "jobs": job_list,
>
> "running": scheduler.is_running(), "universe": config.DEFAULT_UNIVERSE,
>
> "schedule": f"周一至周五 {config.INGEST_HOUR:02d}:{config.INGEST_MINUTE:02d}"}
>
>
>
>
>
> @app.post("/api/admin/ingest")
>
> def admin_ingest():
>
> if scheduler.is_running():
>
> return {"started": False, "msg": "已有入库任务在执行"}
>
> return scheduler.trigger_async()
>
>
>
>
>
> @app.post("/api/admin/ingest_all")
>
> def admin_ingest_all():
>
> return scheduler.trigger_all_async()
>
>
>
>
>
> @app.get("/api/db/kline")
>
> def db_kline(symbol: str = Query("600519"), days: int = Query(250, ge=20, le=1000)):
>
> with get_session() as s:
>
> rows = s.execute(
>
> select(DailyQuote).where(DailyQuote.code == symbol)
>
> .order_by(DailyQuote.date.desc()).limit(days)
>
> ).scalars().all()
>
> rows = list(reversed(rows))
>
> if not rows:
>
> return {"source": "db", "empty": True, "symbol": symbol, "dates": [], "ohlc": [], "vols": []}
>
> return {"source": "db", "symbol": symbol,
>
> "dates": [r.date.strftime("%m/%d") for r in rows],
>
> "ohlc": [[r.open, r.close, r.low, r.high] for r in rows],
>
> "vols": [r.volume for r in rows]}
>
>
>
>
>
> @app.get("/api/db/sentiment_history")
>
> def db_sentiment_history(days: int = Query(60, ge=5, le=365)):
>
> with get_session() as s:
>
> rows = s.execute(select(SentimentDaily).order_by(SentimentDaily.date.desc()).limit(days)).scalars().all()
>
> rows = list(reversed(rows))
>
> return {"dates": [r.date.isoformat() for r in rows],
>
> "up": [r.up for r in rows], "down": [r.down for r in rows],
>
> "limit_up": [r.limit_up for r in rows]}
>
>
>
>
>
> @app.get("/api/review/daily")
>
> def review_daily(date: str = Query(None)):
>
> with get_session() as s:
>
> if date:
>
> d = dt.date.fromisoformat(date)
>
> else:
>
> d = s.execute(select(func.max(SectorDaily.date))).scalar()
>
> if not d:
>
> return {"ok": False, "msg": "暂无入库数据,请先在数据中台执行入库"}
>
> sectors = s.execute(select(SectorDaily).where(SectorDaily.date == d).order_by(SectorDaily.pct.desc())).scalars().all()
>
> flows = s.execute(select(FundFlowDaily).where(FundFlowDaily.date == d).order_by(FundFlowDaily.net.desc())).scalars().all()
>
> senti = s.execute(select(SentimentDaily).where(SentimentDaily.date == d)).scalar_one_or_none()
>
> lhb = s.execute(select(DragonTiger).where(DragonTiger.date == d).order_by(DragonTiger.net.desc()).limit(10)).scalars().all()
>
>
>
> top_sec = [{"name": x.name, "pct": x.pct} for x in sectors[:8]]
>
> bot_sec = [{"name": x.name, "pct": x.pct} for x in sectors[-5:]]
>
> inflow = [{"name": x.name, "net": x.net} for x in flows[:8]]
>
> outflow = [{"name": x.name, "net": x.net} for x in flows[-5:][::-1]]
>
> senti_d = ({"up": senti.up, "down": senti.down, "limit_up": senti.limit_up,
>
> "limit_down": senti.limit_down} if senti else None)
>
>
>
> summary = _gen_review_text(d, senti_d, top_sec, inflow)
>
> return {"ok": True, "date": d.isoformat(), "sentiment": senti_d,
>
> "top_sectors": top_sec, "weak_sectors": bot_sec,
>
> "inflow": inflow, "outflow": outflow,
>
> "dragon": [{"name": x.name, "code": x.code, "net": x.net, "pct": x.pct} for x in lhb],
>
> "summary": summary}
>
>
>
>
>
> def _gen_review_text(d, senti, top_sec, inflow):
>
> parts = [f"【{d.isoformat()} 复盘】"]
>
> if senti:
>
> tone = "情绪偏暖" if senti["up"] > senti["down"] else "情绪偏弱"
>
> parts.append(f"全市场上涨 {senti['up']} 家、下跌 {senti['down']} 家,涨停 {senti['limit_up']} 家、跌停 {senti['limit_down']} 家,{tone}。")
>
> if top_sec:
>
> names = "、".join(x["name"] for x in top_sec[:3])
>
> parts.append(f"领涨板块:{names}。")
>
> if inflow:
>
> names = "、".join(x["name"] for x in inflow[:3])
>
> parts.append(f"主力净流入居前:{names}。")
>
> parts.append("注以上为基于入库数据的自动统计AI 智能点评将在 AI 分析模块接入大模型后生成。")
>
> return " ".join(parts)
>
>
>
>
>
> @app.get("/api/backtest")
>
> def backtest_api(symbol: str = Query("600519"), fast: int = Query(5, ge=2, le=60),
>
> slow: int = Query(20, ge=5, le=250)):
>
> if fast >= slow:
>
> return {"ok": False, "msg": "快线周期需小于慢线周期"}
>
> return bt.run_backtest(symbol, fast, slow)
>
>
>
>
>
> # ============ 全市场选股 ============
>
> STRATEGIES = {
>
> "surge": "最近暴涨5日涨幅≥20%",
>
> "plunge": "最近暴跌5日跌幅≥15%",
>
> "dip": "超跌抄底60日分位≤20%且当日企稳)",
>
> "breakout": "突破走强逼近60日新高",
>
> "ma_bull": "均线多头MA5>10>20",
>
> "volume": "放量上攻量比≥2且上涨",
>
> "macd_gold": "MACD金叉",
>
> "strong": "强势连涨≥3日连阳",
>
> }
>
>
>
>
>
> @app.get("/api/screen/strategies")
>
> def screen_strategies():
>
> return {"list": [{"id": k, "name": v} for k, v in STRATEGIES.items()]}
>
>
>
>
>
> @app.get("/api/screen")
>
> def screen(strategy: str = Query("surge"), limit: int = Query(60, ge=10, le=300),
>
> min_amount: float = Query(0.0)):
>
> M = StockMetric
>
> q = select(M)
>
> order = M.ret5.desc()
>
> if strategy == "surge":
>
> q = q.where(M.ret5 >= 20)
>
> elif strategy == "plunge":
>
> q = q.where(M.ret5 <= -15); order = M.ret5.asc()
>
> elif strategy == "dip":
>
> q = q.where(M.pos60 <= 0.2, M.pct > 0); order = M.pos60.asc()
>
> elif strategy == "breakout":
>
> q = q.where(M.pos60 >= 0.95, M.pct > 0); order = M.ret20.desc()
>
> elif strategy == "ma_bull":
>
> q = q.where(M.ma_bull.is_(True)); order = M.ret20.desc()
>
> elif strategy == "volume":
>
> q = q.where(M.vol_ratio >= 2, M.pct > 0); order = M.vol_ratio.desc()
>
> elif strategy == "macd_gold":
>
> q = q.where(M.macd_gold.is_(True)); order = M.ret5.desc()
>
> elif strategy == "strong":
>
> q = q.where(M.up_streak >= 3); order = M.up_streak.desc()
>
> if min_amount > 0:
>
> q = q.where(M.amount >= min_amount)
>
> q = q.order_by(order).limit(limit)
>
> with get_session() as s:
>
> rows = s.execute(q).scalars().all()
>
> total = s.execute(select(func.count()).select_from(M)).scalar() or 0
>
> return {"strategy": strategy, "name": STRATEGIES.get(strategy, strategy), "pool_size": total,
>
> "count": len(rows), "list": [{
>
> "code": r.code, "name": r.name, "close": r.close, "pct": r.pct,
>
> "ret5": r.ret5, "ret20": r.ret20, "vol_ratio": r.vol_ratio,
>
> "rsi14": r.rsi14, "pos60": round(r.pos60 * 100, 1), "amount": r.amount,
>
> "up_streak": r.up_streak} for r in rows]}
>
>
>
>
>
> @app.get("/api/securities/search")
>
> def securities_search(q: str = Query("", min_length=0), limit: int = Query(15, le=50)):
>
> with get_session() as s:
>
> stmt = select(Security)
>
> if q:
>
> stmt = stmt.where((Security.code.like(f"{q}%")) | (Security.name.like(f"%{q}%")))
>
> rows = s.execute(stmt.limit(limit)).scalars().all()
>
> return {"list": [{"code": r.code, "name": r.name} for r in rows]}
>
>
>
>
>
> # ============ 个股复盘K线 + 买卖点 + 回放) ============
>
> def _ma_list(close, n):
>
> out = [None] * len(close)
>
> for i in range(len(close)):
>
> if i >= n - 1:
>
> out[i] = round(sum(close[i - n + 1:i + 1]) / n, 3)
>
> return out
>
>
>
>
>
> @app.get("/api/review/stock")
>
> def review_stock(symbol: str = Query("600519"), days: int = Query(250, ge=40, le=1000),
>
> fast: int = Query(5), slow: int = Query(20)):
>
> with get_session() as s:
>
> rows = s.execute(
>
> select(DailyQuote).where(DailyQuote.code == symbol)
>
> .order_by(DailyQuote.date.desc()).limit(days)
>
> ).scalars().all()
>
> sec = s.get(Security, symbol)
>
> rows = list(reversed(rows))
>
> if not rows:
>
> return {"ok": False, "msg": "该股票库内无日线,请先在数据中台入库该股或执行全市场回填", "symbol": symbol}
>
> dates = [r.date.strftime("%y/%m/%d") for r in rows]
>
> ohlc = [[r.open, r.close, r.low, r.high] for r in rows]
>
> vols = [r.volume for r in rows]
>
> close = [r.close for r in rows]
>
> maf, mas = _ma_list(close, fast), _ma_list(close, slow)
>
>
>
> signals = []
>
> for i in range(1, len(close)):
>
> if maf[i] is None or mas[i] is None or maf[i - 1] is None or mas[i - 1] is None:
>
> continue
>
> if maf[i - 1] <= mas[i - 1] and maf[i] > mas[i]:
>
> signals.append({"idx": i, "date": dates[i], "price": close[i], "type": "buy"})
>
> elif maf[i - 1] >= mas[i - 1] and maf[i] < mas[i]:
>
> signals.append({"idx": i, "date": dates[i], "price": close[i], "type": "sell"})
>
>
>
> # 区间统计
>
> hi = max(r.high for r in rows); lo = min(r.low for r in rows)
>
> period_ret = round((close[-1] / close[0] - 1) * 100, 2)
>
> return {"ok": True, "symbol": symbol, "name": sec.name if sec else symbol,
>
> "dates": dates, "ohlc": ohlc, "vols": vols,
>
> "ma_fast": maf, "ma_slow": mas, "fast": fast, "slow": slow,
>
> "signals": signals,
>
> "stats": {"period_return": period_ret, "high": hi, "low": lo,
>
> "start": dates[0], "end": dates[-1], "bars": len(rows)}}
>
>
>
>
>
> # ============ AI 分析 ============
>
> @app.get("/api/ai/status")
>
> def ai_status():
>
> return {"enabled": llm.enabled(), "model": config.LLM_MODEL if llm.enabled() else None}
>
>
>
>
>
> @app.get("/api/ai/review_daily")
>
> def ai_review_daily(date: str = Query(None)):
>
> return ai.review_daily_comment(date)
>
>
>
>
>
> @app.get("/api/ai/diagnose")
>
> def ai_diagnose(symbol: str = Query("600519")):
>
> return ai.diagnose(symbol)
>
>
>
>
>
> @app.get("/api/ai/today")
>
> def ai_today():
>
> return ai.today_strategy()
>
>
>
>
>
> # ============ 可回溯:信号历史胜率 + 实测准确率 ============
>
> @app.get("/api/ai/signal_stats")
>
> def ai_signal_stats(horizon: int = Query(5, ge=1, le=20)):
>
> return {"horizon": horizon, "stats": sig.get_stats(horizon)}
>
>
>
>
>
> @app.post("/api/ai/signal_stats/compute")
>
> def ai_signal_stats_compute(sample: int = Query(500, ge=50, le=4000), horizon: int = Query(5, ge=1, le=20)):
>
> return scheduler.trigger_signal_stats_async(sample, horizon)
>
>
>
>
>
> @app.get("/api/ai/accuracy")
>
> def ai_accuracy():
>
> return sig.accuracy()
>
>
>
>
>
> @app.post("/api/ai/accuracy/verify")
>
> def ai_accuracy_verify():
>
> return sig.verify_predictions()
>
>
>
>
>
> # ============ AI 自动复盘日报 ============
>
> @app.get("/api/report/daily")
>
> def report_daily(date: str = Query(None)):
>
> return rpt.get_by_date(date) if date else rpt.latest()
>
>
>
>
>
> @app.get("/api/report/history")
>
> def report_history(limit: int = Query(30, ge=1, le=120)):
>
> return rpt.history(limit)
>
>
>
>
>
> @app.post("/api/report/generate")
>
> def report_generate(date: str = Query(None), push: bool = Query(False)):
>
> return rpt.generate(date, push=push)
>
>
>
>
>
> # ============ 交易日志 & 组合 ============
>
> class TradeIn(BaseModel):
>
> code: str
>
> name: str = ""
>
> side: str = "buy"
>
> price: float
>
> qty: int
>
> fee: float = 0.0
>
> date: str = ""
>
> reason: str = ""
>
> emotion: str = ""
>
>
>
>
>
> @app.get("/api/trades")
>
> def list_trades():
>
> with get_session() as s:
>
> rows = s.execute(select(Trade).order_by(Trade.date.desc(), Trade.id.desc())).scalars().all()
>
> names = {}
>
> return {"list": [{"id": t.id, "date": t.date.isoformat(), "code": t.code, "name": t.name,
>
> "side": t.side, "price": t.price, "qty": t.qty, "fee": t.fee,
>
> "reason": t.reason, "emotion": t.emotion} for t in rows]}
>
>
>
>
>
> @app.post("/api/trades")
>
> def add_trade(t: TradeIn):
>
> d = dt.date.fromisoformat(t.date) if t.date else dt.date.today()
>
> name = t.name
>
> if not name:
>
> with get_session() as s:
>
> sec = s.get(Security, t.code)
>
> name = sec.name if sec else t.code
>
> with get_session() as s:
>
> row = Trade(date=d, code=t.code, name=name, side=t.side, price=t.price,
>
> qty=t.qty, fee=t.fee, reason=t.reason, emotion=t.emotion)
>
> s.add(row); s.commit()
>
> return {"ok": True, "id": row.id}
>
>
>
>
>
> @app.delete("/api/trades/{tid}")
>
> def del_trade(tid: int):
>
> with get_session() as s:
>
> row = s.get(Trade, tid)
>
> if row:
>
> s.delete(row); s.commit()
>
> return {"ok": True}
>
>
>
>
>
> @app.get("/api/portfolio")
>
> def get_portfolio():
>
> return pf.compute()
>
>
>
>
>
> @app.get("/api/portfolio/equity")
>
> def portfolio_equity():
>
> return pf.equity_curve()
>
>
>
>
>
> # ============ 推送通知 ============
>
> @app.get("/api/notify/status")
>
> def notify_status():
>
> return {"channels": notifier.channels_status(), "enabled": notifier.any_enabled()}
>
>
>
>
>
> @app.post("/api/notify/test")
>
> def notify_test():
>
> if not notifier.any_enabled():
>
> return {"ok": False, "msg": "未配置任何推送渠道,请在 backend/.env 配置后重启"}
>
> res = notifier.notify("【智策】推送测试", "这是一条来自智策股票终端的测试通知,收到即表示推送通道正常。")
>
> return {"ok": True, "result": res}
>
>
>
>
>
> # ============ 智能预警 ============
>
> class AlertIn(BaseModel):
>
> code: str
>
> kind: str = "price_above"
>
> threshold: float
>
> note: str = ""
>
>
>
>
>
> @app.get("/api/alerts")
>
> def list_alerts():
>
> with get_session() as s:
>
> rows = s.execute(select(AlertRule).order_by(AlertRule.id.desc())).scalars().all()
>
> return {"list": [{"id": r.id, "code": r.code, "name": r.name, "kind": r.kind,
>
> "threshold": r.threshold, "status": r.status, "note": r.note,
>
> "last_value": r.last_value,
>
> "triggered_at": r.triggered_at.strftime("%m-%d %H:%M") if r.triggered_at else ""}
>
> for r in rows]}
>
>
>
>
>
> @app.post("/api/alerts")
>
> def add_alert(a: AlertIn):
>
> with get_session() as s:
>
> sec = s.get(Security, a.code)
>
> name = sec.name if sec else a.code
>
> row = AlertRule(code=a.code, name=name, kind=a.kind, threshold=a.threshold, note=a.note)
>
> s.add(row); s.commit()
>
> return {"ok": True, "id": row.id}
>
>
>
>
>
> @app.delete("/api/alerts/{aid}")
>
> def del_alert(aid: int):
>
> with get_session() as s:
>
> row = s.get(AlertRule, aid)
>
> if row:
>
> s.delete(row); s.commit()
>
> return {"ok": True}
>
>
>
>
>
> @app.post("/api/alerts/{aid}/reactivate")
>
> def reactivate_alert(aid: int):
>
> with get_session() as s:
>
> row = s.get(AlertRule, aid)
>
> if row:
>
> row.status = "active"; row.triggered_at = None; s.commit()
>
> return {"ok": True}
>
>
>
>
>
> @app.post("/api/alerts/check")
>
> def manual_check():
>
> return al.check_alerts()
>
>
>
>
>
> @app.get("/api/alerts/events")
>
> def alert_events(unread_only: bool = Query(False), limit: int = Query(30, le=100)):
>
> with get_session() as s:
>
> stmt = select(AlertEvent).order_by(AlertEvent.id.desc())
>
> if unread_only:
>
> stmt = stmt.where(AlertEvent.read.is_(False))
>
> rows = s.execute(stmt.limit(limit)).scalars().all()
>
> unread = s.execute(select(func.count()).select_from(AlertEvent).where(AlertEvent.read.is_(False))).scalar() or 0
>
> return {"unread": unread, "list": [{"id": e.id, "code": e.code, "name": e.name,
>
> "message": e.message, "time": e.created_at.strftime("%m-%d %H:%M:%S") if e.created_at else ""}
>
> for e in rows]}
>
>
>
>
>
> @app.post("/api/alerts/events/read")
>
> def mark_events_read():
>
> with get_session() as s:
>
> for e in s.execute(select(AlertEvent).where(AlertEvent.read.is_(False))).scalars():
>
> e.read = True
>
> s.commit()
>
> return {"ok": True}
>
>
>
>
>
> # ============ 资讯中心 ============
>
> @app.get("/api/news")
>
> def news(limit: int = Query(40, le=100)):
>
> return svc.get_news(limit)
>
>
>
>
>
> @app.get("/api/news/stock")
>
> def news_stock(code: str = Query(...)):
>
> return svc.get_stock_news(code)
>
>
>
>
>
> @app.get("/api/news/watch")
>
> def news_watch():
>
> codes = load_watch()[:6]
>
> out = []
>
> for c in codes:
>
> r = svc.get_stock_news(c, limit=4)
>
> for x in r["list"]:
>
> x["code"] = c
>
> out.append(x)
>
> out.sort(key=lambda x: x["time"], reverse=True)
>
> return {"list": out[:40]}
>
>
>
>
>
> class NewsAI(BaseModel):
>
> title: str
>
> content: str = ""
>
>
>
>
>
> @app.post("/api/news/ai")
>
> def news_ai(n: NewsAI):
>
> text_in = (n.title + "。" + n.content).strip()
>
> senti, kw = svc.judge_sentiment(text_in)
>
> if llm.enabled():
>
> try:
>
> prompt = ("请分析下面这条财经资讯:\n"
>
> "1) 一句话摘要2) 利好/利空/中性判断及理由3) 可能受影响的板块或个股方向。120字内。\n\n"
>
> + text_in[:1200])
>
> text = llm.ask(prompt, temperature=0.3, max_tokens=400)
>
> return {"ok": True, "source": "llm", "sentiment": senti, "text": text}
>
> except Exception:
>
> pass
>
> return {"ok": True, "source": "rule", "sentiment": senti,
>
> "text": f"判断:{senti}(关键词:{'、'.join(kw) or '无'})。摘要:{text_in[:80]}…\n配置大模型后可获得更深入的关联分析"}
>
>
>
>
>
> # ============ 静态前端 ============
>
> FRONTEND_DIR = os.path.join(os.path.dirname(BASE_DIR), "prototype")
>
> if os.path.isdir(FRONTEND_DIR):
>
> app.mount("/", StaticFiles(directory=FRONTEND_DIR, html=True), name="frontend")
>
>
>
>
>
> if __name__ == "__main__":
>
> import uvicorn
>
> uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=False)
>
>
#### _Assistant_
> 基于对项目的分析,我发现以下可优化的功能点:
>
> ## 高优先级优化
>
> ### 1. **缓存与性能优化**
> - `akshare_service.py` 使用内存缓存,服务重启后失效。建议引入 Redis 持久化缓存,避免频繁调用 AkShare API
> - 大量接口未做分页,如 `/api/trades`、`/api/alerts/events` 在数据量大时会有性能问题
> - K线数据查询未添加索引优化提示
>
> ### 2. **错误处理与容错**
> - 多数接口缺少统一的异常捕获,数据源异常时可能返回 500 错误
> - `scheduler.py` 定时任务失败后缺少重试机制和告警
> - AkShare 数据源不稳定时,降级逻辑不够完善(只有 mock 数据)
>
> ### 3. **数据完整性**
> - 交易记录(`trades`)无事务一致性保证,买卖不匹配时持仓计算可能出错
> - 缺少数据修正接口,入库错误数据后无法手动修正
> - 历史数据回填(`ingest_all`)无断点续传,中断后需重新开始
>
> ### 4. **安全性**
> - 所有接口无鉴权,任何人可访问管理功能(`/api/admin/*`)
> - 环境变量敏感信息(`LLM_API_KEY`等)未加密存储
> - CORS 配置为 `allow_origins=["*"]`,生产环境不安全
>
> ## 中优先级优化
>
> ### 5. **功能增强**
> - AI 诊断结果无历史记录,无法追溯分析质量变化
> - 预警系统只支持价格/涨跌幅,缺少技术指标预警(如 MACD 金叉、RSI 超买等)
> - 复盘日报只能手动触发推送,未自动推送给用户
> - 自选股只支持单一列表,无分组管理功能
>
> ### 6. **代码质量**
> - `main.py` 超过 500 行,建议拆分为多个 Router 模块
> - 缺少单元测试和集成测试
> - 部分函数职责不清晰,如 `_gen_review_text()` 混合了业务逻辑和文案生成
>
> ### 7. **监控与日志**
> - 缺少接口访问日志和性能监控
> - 定时任务执行日志只保留在数据库,无实时监控面板
> - AkShare 调用频率无监控,可能触发限流
>
> ## 低优先级优化
>
> ### 8. **用户体验**
> - 前端刷新后丢失自选股筛选条件
> - K线图无技术指标叠加功能(如 MACD、KDJ)
> - 移动端适配不足
>
> ### 9. **扩展性**
> - 只支持 A 股,缺少港股、美股扩展能力
> - 数据源绑定 AkShare,切换成本高
> - 多组合管理需要大幅重构 `portfolio.py`
>
> ## 建议优先实现的 3 个功能
>
> 1. **Redis 缓存层** - 提升响应速度,减少 API 调用
> 2. **统一异常处理中间件** - 提升系统稳定性
> 3. **基础鉴权机制** - 保护管理接口安全
>
> 需要我深入分析某个模块的优化方案吗?
#### _User_
> 功能上有没有好的功能拓展建议
#### _Assistant_
> 基于当前架构,建议以下功能扩展方向:
>
> ## 一、高价值功能扩展
>
> ### 1. **盘中实时监控雷达** ⭐⭐⭐⭐⭐
> **痛点**:当前只有收盘后复盘,错过盘中异动机会
>
> **功能点**
> - 快速拉升监控5分钟涨幅 >3%
> - 放量突破预警(量比 >3 且突破关键位)
> - 涨停打开/炸板追踪
> - 连板股实时追踪
> - 大单异动(单笔超百万)
>
> **实现难度**:中等(需要分钟线数据源)
>
> ### 2. **策略回测增强** ⭐⭐⭐⭐⭐
> **痛点**:当前只有简单 MA 交叉,无法验证复杂策略
>
> **功能点**
> - 多因子组合回测(技术+基本面)
> - 仓位管理策略(金字塔加仓、止损止盈)
> - 参数优化网格搜索
> - 夏普比率/最大回撤/胜率等完整指标
> - 策略对比(并排显示多条净值曲线)
> - 交易明细导出(每笔买卖点、持仓天数、收益率)
>
> **实现难度**:中等
>
> ### 3. **板块轮动分析** ⭐⭐⭐⭐
> **痛点**:只有当日板块涨跌,看不出资金流向趋势
>
> **功能点**
> - 板块强弱排名趋势近5日/10日/20日
> - 资金流向桑基图(从哪个板块流向哪个板块)
> - 板块生命周期判断(启动期/加速期/衰退期)
> - 龙头股自动识别(板块内涨幅+成交额排名)
> - 板块联动性分析(相关系数热力图)
>
> **实现难度**:中等
>
> ### 4. **智能选股增强** ⭐⭐⭐⭐
> **痛点**:当前选股策略固定,无法自定义
>
> **功能点**
> - 可视化选股器(拖拽式多条件组合)
> - 选股结果回测验证(该选股规则历史表现)
> - 选股策略保存/分享
> - 条件预警(符合条件时自动推送)
> - 选股结果对比(今日 vs 昨日,哪些股票新入选/退出)
>
> **实现难度**:中高
>
> ### 5. **持仓归因分析深化** ⭐⭐⭐⭐
> **痛点**:当前只有简单盈亏,不知道赚钱/亏钱原因
>
> **功能点**
> - 收益归因分解:选股能力 vs 择时能力 vs 运气成分
> - 持仓时长分析(短线/中线/长线各自胜率)
> - 买入理由有效性验证(按理由统计胜率)
> - 情绪标签相关性(贪婪/恐惧/理性,各自收益率)
> - 对标指数超额收益拆解
>
> **实现难度**:中等
>
> ## 二、差异化功能扩展
>
> ### 6. **AI 对话式分析** ⭐⭐⭐⭐⭐
> **亮点**:与大模型深度结合,打造对话式炒股助手
>
> **功能点**
> - 自然语言选股:"帮我找近期突破且资金流入的科技股"
> - 持仓诊断对话:"我的持仓有什么风险?"
> - 策略建议:"当前市场适合什么策略?"
> - 实时问答:"某某股票为什么今天大跌?"
> - 上下文记忆(多轮对话,记住用户偏好)
>
> **实现难度**:中高(需要 Function Calling
>
> ### 7. **社区情绪监控** ⭐⭐⭐⭐
> **亮点**:量化散户情绪,反向指标
>
> **功能点**
> - 爬取东方财富/雪球/股吧热帖
> - 情绪指数(乐观/悲观比例)
> - 热议股票排行(讨论量激增预警)
> - 关键词云图(当前市场关注什么)
> - 情绪与股价相关性回测
>
> **实现难度**:中高(需要爬虫+NLP
>
> ### 8. **事件驱动策略** ⭐⭐⭐⭐
> **亮点**:基于财经事件的量化交易
>
> **功能点**
> - 财报发布前后统计规律
> - 限售解禁影响回测
> - 高管增持/减持跟踪
> - 行业政策事件库(光伏补贴、新能源政策等)
> - 事件驱动选股(财报超预期+高管增持)
>
> **实现难度**:中高
>
> ### 9. **模拟盘对战** ⭐⭐⭐
> **亮点**:用户间 PK增加粘性
>
> **功能点**
> - 创建模拟盘对战房间
> - 排行榜(收益率/夏普比率)
> - 查看高手持仓(匿名化)
> - 复盘对比(我为什么输给他)
> - 月度/季度冠军
>
> **实现难度**:中等
>
> ### 10. **财报深度解读** ⭐⭐⭐⭐
> **痛点**:技术分析为主,缺少基本面支持
>
> **功能点**
> - 财报关键指标趋势ROE/毛利率/营收增速)
> - AI 财报摘要(一句话总结)
> - 同行对比(与行业均值对比)
> - 财报异常预警(存货激增、应收账款占比过高)
> - 财报发布日历提醒
>
> **实现难度**:中等
>
> ## 三、实用小功能
>
> ### 11. **交易日历与提醒**
> - 除权除息提醒
> - 解禁日期预警
> - 业绩预告/快报发布日历
> - 股东大会日期
>
> ### 12. **成本计算器**
> - 精确计算交易成本(印花税/佣金/过户费)
> - 补仓成本计算
> - 持仓成本线标注在 K 线图上
>
> ### 13. **涨跌停分析**
> - 连板股追踪器(今日几进几?)
> - 炸板率统计(某股涨停后次日表现)
> - 涨停敢死队排行
>
> ### 14. **关联股票发现**
> - 同一题材股票聚类
> - 供应链上下游关系图
> - 股东关联关系挖掘
>
> ### 15. **数据导出与备份**
> - 持仓数据导出 Excel
> - 交易记录导出
> - 选股结果导出
> - 数据库自动备份
>
> ## 建议优先级排序ROI 角度)
>
> | 排名 | 功能 | 投入成本 | 用户价值 | 差异化 |
> |------|------|----------|----------|--------|
> | 1 | AI 对话式分析 | 高 | 极高 | 极高 |
> | 2 | 盘中实时监控雷达 | 中 | 极高 | 高 |
> | 3 | 策略回测增强 | 中 | 高 | 中 |
> | 4 | 板块轮动分析 | 中 | 高 | 中 |
> | 5 | 持仓归因分析深化 | 低 | 高 | 中 |
>
> **快速见效(先做)**
> 1. 盘中实时监控雷达
> 2. 策略回测增强
> 3. 持仓归因分析深化
>
> **长期竞争力(后做)**
> 1. AI 对话式分析
> 2. 社区情绪监控
> 3. 事件驱动策略
>
> 需要我详细设计某个功能的技术方案吗?