1321 lines
51 KiB
Markdown
1321 lines
51 KiB
Markdown
### [Continue](https://continue.dev) session transcript
|
||
Exported: 6/13/2026, 4:17:09 PM
|
||
|
||
#### _User_
|
||
|
||
> 你是什么模型 版本号多少
|
||
|
||
#### _Assistant_
|
||
|
||
> 我是 Claude,由 Anthropic 开发,具体是 Claude Opus 4.6(模型 ID:claude-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 + 原生 JS,ECharts 5(CDN)
|
||
>
|
||
> - **后端**: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` | ETL:AkShare → 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 + 原生 JS,ECharts 5(CDN) |
|
||
>
|
||
> | 后端 | Python 3.12 + FastAPI 0.115 + uvicorn |
|
||
>
|
||
> | 数据库 | PostgreSQL(psycopg2-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 Compose(FastAPI + PostgreSQL),Nginx 反代 |
|
||
>
|
||
>
|
||
|
||
#### _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. 事件驱动策略
|
||
>
|
||
> 需要我详细设计某个功能的技术方案吗? |