同步项目文档:PostgreSQL 迁移、FastAPI 服务、UUID 主键与暗夜模式

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
fish
2026-05-03 15:47:23 +08:00
parent c64def9031
commit 23776b5e96
2 changed files with 93 additions and 44 deletions

315
README.md Normal file
View File

@@ -0,0 +1,315 @@
# 期货行情分析系统 — 使用说明
基于 Docker + Python(tushare) + PostgreSQL 的中国期货行情分析系统。当前阶段已实现数据采集、三层加权打分模型、Bark 推送通知与 Web 报表浏览端。运行方式支持两种模式:① 宿主机定时器触发 `docker-compose run` 执行 CLI;② 通过 FastAPI HTTP API 服务触发。
## 环境准备
- Docker >= 20.10
- Docker Compose >= 2.0
- (可选) psql 或任意 PostgreSQL 客户端用于本地查库
## 快速开始
### 1. 配置 tushare token
将 token 写入 `tushare/.env`
```bash
echo "TUSHARE_TOKEN=你的token" > tushare/.env
```
该文件已被 gitignore 排除,不会进入版本库。
### 2. 启动全栈服务
```bash
docker-compose up -d
```
这会同时启动 PostgreSQL、tushare API 服务(端口 8000)与 Web 浏览端(端口 8080)。
### 3. 通过 CLI 跑当月主力
```bash
docker-compose run --rm tushare python -m src.main
```
不传参时,按 `tushare/src/contracts.py``ROLLOVER_RULES` 自动选 FG 玻璃当月主力(例如 2026-05 -> `FG2609.ZCE`),启动后会先打印 `[AUTO] FG 当月主力 -> ...`,然后:
1. 从 tushare 拉取合约日线数据
2. 写入 PostgreSQL `futures` 数据库
3. 运行三层打分模型
4. 保存打分结果并输出到 stdout
5. 通过 Bark 推送评分摘要
### 4. 通过 API 触发流水线
```bash
# 触发 FG 打分
curl -X POST http://localhost:8000/api/v1/run -H "Content-Type: application/json" \
-d '{"symbol":"FG"}'
# 查询最新打分
curl "http://localhost:8000/api/v1/scores?limit=5"
# 查询合约列表
curl "http://localhost:8000/api/v1/contracts"
# 查询 K 线数据
curl "http://localhost:8000/api/v1/candles?ts_code=FG2609.ZCE"
```
### 5. 跑其他合约或品种
```bash
# 显式指定合约
docker-compose run --rm tushare python -m src.main RB2510.SHF
docker-compose run --rm tushare python -m src.main I2601.DCE
# 按品种代号自动选当月主力(目前只配置了 FG)
docker-compose run --rm tushare python -m src.main --symbol FG
```
### 6. 玻璃 FG 主力轮换规则
| 当前自然月 | 主力合约 |
|----------|---------|
| 1、2、3 月 | 当年 05 |
| 4、5、6、7 月 | 当年 09 |
| 8、9、10、11 月 | **次年** 01 |
| 12 月 | **次年** 05 |
## 三层打分模型
### 综合分数公式
```
综合分数 = (短期动力 × 0.4) + (中期趋势 × 0.35) + (长期结构 × 0.25)
```
### 1. 短期动力7 日窗口,权重 0.4
逐日打分后取均值:
| 持仓变化 | 价格方向 | 得分 |
|---------|---------|------|
| 增仓 | 上涨 | 100多头主动进攻 |
| 增仓 | 下跌 | 0空头主动进攻 |
| 减仓 | 上涨 | 70空头撤退 |
| 减仓 | 下跌 | 30多头撤退 |
| 持平(\|变化\|<1% | 上涨 | 60 |
| 持平(\|变化\|<1% | 下跌 | 40 |
### 2. 中期趋势15 日窗口,权重 0.35
```
价格信号 = (今收 - 15日前收) / 15日前收
价格信号得分 = clamp(50 + 收益率×500, 0, 100)
资金意愿:
增仓上涨天数 > 增仓下跌天数 → 80
两者相当 → 50
增仓下跌天数 > 增仓上涨天数 → 20
模块得分 = 价格信号 × 0.6 + 资金意愿 × 0.4
```
### 3. 长期结构30 日窗口,权重 0.25
```
持仓变化幅度 = (30日日均持仓 - 30日前持仓) / 30日前持仓
> 10% → 90显著增仓
5%~10% → 70温和增仓
-5%~5% → 50基本持平
-10%~-5% → 30温和减仓
< -10% → 10显著减仓
```
### 信号解读
| 综合分数 | 信号 |
|---------|------|
| 80-100 | 强烈看多 — 价格与资金共振 |
| 50-80 | 偏多/震荡偏强 |
| 40-50 | 偏空/震荡偏弱 |
| 0-40 | 强烈看空 — 资金主动打压 |
## 数据查询
业务数据存储在 PostgreSQL 中,可通过以下方式查询:
```bash
# 查看最新打分
docker-compose exec postgres psql -U trade -d futures -c \
"SELECT ts_code, trade_date, composite, signal FROM scores ORDER BY trade_date DESC LIMIT 5;"
# 查看合约日线
docker-compose exec postgres psql -U trade -d futures -c \
"SELECT trade_date, open, high, low, close, vol, oi FROM candles WHERE ts_code='FG2609.ZCE' ORDER BY trade_date DESC LIMIT 10;"
# 或通过 API 查询
curl "http://localhost:8000/api/v1/scores?ts_code=FG2609.ZCE&limit=10"
curl "http://localhost:8000/api/v1/candles?ts_code=FG2609.ZCE"
```
## 项目结构
```
trade/
├── docker-compose.yml # Docker Compose 编排(postgres + tushare + web)
├── 使用说明.md # 本文件
├── CLAUDE.md # Claude Code 项目指引
├── data/ # auth.db 目录(gitignored)
│ └── auth.db # web 自己维护的用户表(SQLite)
├── .gitignore # Git 忽略配置
├── tushare/ # Python 数据服务
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── .env # TUSHARE_TOKEN(本地,不入库)
│ └── src/ # 数据采集 + 打分 + Bark 推送 + FastAPI
│ ├── api.py # FastAPI 服务入口
│ ├── models.py
│ ├── fetcher.py
│ ├── scorer.py
│ ├── storage.py # PostgreSQL 读写
│ ├── contracts.py
│ ├── notifier.py
│ └── main.py # CLI 入口
└── web/ # Web 浏览端
├── .dockerignore
├── backend/ # Go 1.25 后端 (chi + lib/pq + JWT)
│ ├── Dockerfile # 多阶段:node 构 UI → go 构二进制 → alpine 运行
│ ├── go.mod
│ ├── main.go
│ ├── embed.go # //go:embed all:dist
│ ├── .env.example # ADMIN_USER/ADMIN_PASS/JWT_SECRET 示例
│ ├── dist/ # 占位,Docker 构建期被 vite 输出覆盖
│ └── internal/
│ ├── config/ # 环境变量加载
│ ├── store/ # PostgreSQL 业务查询 + SQLite auth.db
│ ├── auth/ # JWT + bcrypt + 首启 admin 引导
│ ├── middleware/ # RequireUser / RequireAdmin / 日志
│ ├── handlers/ # 登录 / 打分 / K线 / 用户管理
│ └── router/ # chi 路由装配
└── frontend/ # Vue 3 + Vite + Element Plus + ECharts
├── package.json
├── vite.config.ts
├── tsconfig.json
├── index.html
└── src/
├── main.ts / App.vue
├── router/ # 守卫(未登录/管理员路由)
├── stores/ # Pinia: auth.ts(持久化 token) + theme.ts(暗/浅色模式)
├── api/ # axios 封装 + 各端点
├── views/ # 登录 / 打分列表 / 图表 / 用户管理
└── components/ # 抽屉 + ECharts K 线
```
## 技术栈
- **Python 3.13** (alpine) + **tushare** + **pandas** + **FastAPI** + **psycopg3** — 数据采集、打分与 API 服务
- **Go 1.25.8** (alpine 3.23) + **chi** + **lib/pq** + **JWT** — Web 后端
- **Vue 3** + **Vite** + **Element Plus** + **ECharts** — Web 前端
- **PostgreSQL 18.3** (alpine 3.23) — 业务数据存储
- **SQLite** — 鉴权数据存储(auth.db)
- **Docker / Docker Compose** — 容器化部署
## 常见问题
**Q: 为什么某些日期返回空数据?**
A: tushare 数据更新有延迟,且不同接口对 token 积分等级有要求。若 `fut_daily` 返回空但 `trade_cal` 正常,通常是该日期实际行情数据尚未入库。
**Q: 合约代码格式?**
A: 郑商所用 `.ZCE` 后缀(如 `FG2609.ZCE`),上期所用 `.SHF`,大商所用 `.DCE`。注意不是 `.CZC`
**Q: 如何定时自动跑?**
A: 通过宿主机 cron / launchd 等定时器调用 `docker-compose run --rm tushare ...`。打分结束会通过 Bark 推送结果(见 `tushare/src/notifier.py`)。也可直接调用 API: `curl -X POST http://localhost:8000/api/v1/run ...`
## Web 报表(浏览端)
`./web/` 提供一个图形化的浏览端,展示 tushare 流水线写入 PostgreSQL 的打分与行情数据。后端 Go(`golang:1.25.8-alpine3.23`)读取数据库,前端 Vue 3 + Element Plus + ECharts,通过 docker-compose 一起部署。
### 1. 配置首启凭据
`web/backend/.env` 写入(`.env` 已 gitignored,可参考 `web/backend/.env.example`):
```bash
ADMIN_USER=admin
ADMIN_PASS=请改成强密码
JWT_SECRET=$(openssl rand -hex 32)
```
`ADMIN_USER`/`ADMIN_PASS` 仅在 `auth.db` 中没有任何 admin 时生效,首次启动会以这一对凭据建立管理员;之后即使改这两个变量也不会改密。`JWT_SECRET` 必须 ≥16 字符。
### 2. 启动
```bash
# 构建并启动 web 服务,不影响现有 tushare
docker-compose up -d --build web
# 查看启动日志:首启会出现 [bootstrap] admin 'xxx' created
docker-compose logs -f web
```
浏览器访问 `http://localhost:8080`,用上一步的管理员账号登录。
### 3. 页面说明
- **打分列表** `/scores`:按合约、日期、条数筛选,展示综合分/信号/三层得分;点击「明细」弹抽屉,显示短期 7 日逐日打分、中期(15d)价格收益与资金意愿、长期(30d)持仓变化。
- **K 线 / 持仓** `/chart`:选合约 + 日期区间,主图蜡烛(开高低收),副图持仓量曲线;鼠标拖选缩放。
- **用户管理** `/admin/users`:仅管理员可见。可创建子账号(`user` 默认,亦可建 `admin`)、重置密码、禁用/启用、删除;不允许对自己执行禁用或删除。
普通用户登录后 `/admin/*` 路径会被前端守卫拦截并跳回 `/scores`,后端也会以 403 拒绝。
前端支持暗/浅色模式切换,点击顶部导航栏的「暗/亮」开关即可切换。侧边导航在暗色模式使用深色背景,浅色模式使用浅色背景。
### 4. 子账号维护流程
1. 用 admin 登录 → 进入 `/admin/users` → 「新建账号」,填写用户名 / 密码(≥6 位) / 角色。
2. 把账号发给同事即可登录;无注册入口。
3. 离职 / 风险事件:用「禁用」临时停用(token 立即失效,前端不能再请求),或「删除」彻底清除。
### 5. 数据流向与数据库分离
```
tushare(写) → PostgreSQL futures 数据库 ←(只读)── web 后端
web 后端 ←(读写)→ data/auth.db (SQLite)
```
业务数据(`candles` + `scores`)统一存储在 PostgreSQL 中,`auth.db` 表为:
```sql
users(id, username UNIQUE, password_hash, role IN ('admin','user'),
disabled, created_at, updated_at)
```
`auth.db``./data/` 目录,被 `.gitignore` 覆盖。
### 6. 常见问题
**Q: 忘记管理员密码怎么办?**
```bash
docker-compose stop web
sqlite3 data/auth.db "DELETE FROM users WHERE role='admin';"
# 修改 web/backend/.env 里的 ADMIN_USER/ADMIN_PASS
docker-compose up -d web
```
启动时会重新触发 bootstrap 写入新的 admin。
**Q: 改了 Go / Vue 代码但页面没变?**
源码不挂载,镜像内是 COPY 进去的。重建:`docker-compose build web && docker-compose up -d web`
**Q: 登录提示 "JWT_SECRET 必须至少 16 个字符"?**
`web/backend/.env` 没设或太短,用 `openssl rand -hex 32` 生成一个 64 字符的十六进制字符串即可。
**Q: 为什么 tushare 容器启动后没有立即退出?**
因为默认命令改为 `uvicorn src.api:app` 常驻 API 服务。如需执行单次 CLI,用 `docker-compose run --rm tushare python -m src.main ...`