From 23776b5e96a50f89caa8bfd2ef0c33dfee3e06e2 Mon Sep 17 00:00:00 2001 From: fish Date: Sun, 3 May 2026 15:47:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E9=A1=B9=E7=9B=AE=E6=96=87?= =?UTF-8?q?=E6=A1=A3=EF=BC=9APostgreSQL=20=E8=BF=81=E7=A7=BB=E3=80=81FastA?= =?UTF-8?q?PI=20=E6=9C=8D=E5=8A=A1=E3=80=81UUID=20=E4=B8=BB=E9=94=AE?= =?UTF-8?q?=E4=B8=8E=E6=9A=97=E5=A4=9C=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- CLAUDE.md | 40 ++++++++++++----- 使用说明.md => README.md | 97 +++++++++++++++++++++++++++------------- 2 files changed, 93 insertions(+), 44 deletions(-) rename 使用说明.md => README.md (68%) diff --git a/CLAUDE.md b/CLAUDE.md index 21b4b83..c79a124 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,13 +4,16 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## 项目概述 -基于 Docker + Python(tushare) 的中国期货行情分析系统,实现日线数据采集、三层加权打分模型与 Bark 推送通知。运行方式定位为脚本自动化(宿主机 cron/launchd 等定时调用 `docker-compose run`),不规划独立后端服务。详细业务说明见 `使用说明.md`。 +基于 Docker + Python(tushare) + PostgreSQL 的中国期货行情分析系统,实现日线数据采集、三层加权打分模型与 Bark 推送通知。运行方式支持两种模式:① 宿主机 cron/launchd 定时调用 `docker-compose run` 执行 CLI;② 通过 FastAPI 服务以 HTTP API 触发。详细业务说明见 `README.md`。 ## 常用命令 ```bash -# 不传参 = 按当月 FG 主力自动选合约(轮换规则见 contracts.py:ROLLOVER_RULES) -docker-compose run --rm tushare +# === 启动全栈服务(PostgreSQL + tushare API + web) === +docker-compose up -d + +# === tushare CLI(不传参 = 按当月 FG 主力自动选合约,轮换规则见 contracts.py:ROLLOVER_RULES) === +docker-compose run --rm tushare python -m src.main # 显式指定合约(注意交易所后缀:.ZCE/.SHF/.DCE,郑商所是 .ZCE 不是 .CZC) docker-compose run --rm tushare python -m src.main RB2510.SHF @@ -18,11 +21,21 @@ docker-compose run --rm tushare python -m src.main RB2510.SHF # 用品种代号自动选当月主力(目前只配置了 FG) docker-compose run --rm tushare python -m src.main --symbol FG -# 修改 tushare/src/ 下任意 .py 后必须重建镜像 -docker-compose build tushare +# === tushare API 服务(容器内运行 uvicorn,端口 8000) === +# 触发单次流水线 +curl -X POST http://localhost:8000/api/v1/run -H "Content-Type: application/json" \ + -d '{"symbol":"FG"}' -# 查最新打分 -sqlite3 data/futures.db "SELECT ts_code, trade_date, composite, signal FROM scores ORDER BY trade_date DESC LIMIT 5;" +# 查询打分列表 +curl "http://localhost:8000/api/v1/scores?limit=5" + +# === 修改代码后必须重建镜像 === +docker-compose build tushare +docker-compose build web + +# === 查最新打分(PostgreSQL) === +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;" ``` `tushare/.env` 必须存在且含 `TUSHARE_TOKEN=xxx`(已 gitignored)。可选 `BARK_KEY` 覆盖 `notifier.py` 默认 key。 @@ -31,13 +44,15 @@ sqlite3 data/futures.db "SELECT ts_code, trade_date, composite, signal FROM scor **单进程串行流水线**:`src.main.main()` 先按命令行参数(显式 `ts_code` 优先,否则 `contracts.active_contract(symbol)` 按当月主力自动选)定下合约,再调 `run()` 顺序执行 `fetcher → storage(candles) → scorer → storage(scores) → notifier`。无后台任务、无队列,每次 CLI 调用处理一个合约一日。 +**FastAPI 服务**(`src.api`):容器默认以 `uvicorn src.api:app` 启动,暴露 `/api/v1/run`(触发流水线)、`/api/v1/scores`、`/api/v1/scores/{id}`、`/api/v1/contracts`、`/api/v1/candles` 等端点。启动时自动 `storage.init_db()` 建表。API 与 CLI 共用同一套 `fetcher/storage/scorer/notifier` 逻辑。 + **主力轮换规则**(`contracts.py`):每个品种在 `ROLLOVER_RULES` 中维护 `month -> (主力月, 年份偏移)` 表。FG 当前规则:1-3/12 月→05、4-7 月→09、8-11 月→01,其中 8-11 月与 12 月跨年(`year_offset=1`)。新增品种(如 RB、I)只需在该 dict 里加一条,无需改 main 流程。 **三层打分模型**(`scorer.py`):综合 = 短期(7日,0.4) + 中期(15日,0.35) + 长期(30日,0.25)。`score_daily()` 要求 DataFrame ≥31 行,`fetcher.fetch_contract` 默认拉一个合约的全历史(实际 100+ 行),按 `trade_date` 升序排列后供打分使用。打分结果通过 `dataclass ScoreResult` + `ScoreDetail` 流转,`storage.save_score` 把 detail 序列化为 `detail_json` 文本列。 -**SQLite 作为唯一数据面**:`storage.py` 的 `candles` 与 `scores` 两表都用 `INSERT OR REPLACE`(候选键 `(ts_code, trade_date)`)实现幂等,可反复重跑同一天。`PRAGMA journal_mode=WAL`,提升并发读写。表结构在 `init_db()` 中维护,新增字段需同步该函数。 +**PostgreSQL 作为业务数据库**:docker-compose 编排 `postgres:18.3-alpine3.23`,`tushare` 与 `web` 服务均通过 `DATABASE_URL` 连接。`storage.py` 使用 `psycopg3` 驱动,`candles` 与 `scores` 表以 `ON CONFLICT (ts_code, trade_date) DO UPDATE` 实现幂等,可反复重跑同一天。`scores` 表主键为 `UUID DEFAULT uuidv7() PRIMARY KEY`(见 `models.py` 与 `storage.py`)。 -**Docker 边界**:`docker-compose.yml` 仅把 `./data` 挂为 `/app/data`(数据持久化);`tushare/src/` 是在 Dockerfile 的 `COPY --chown=app:app src ./src` 阶段拷进镜像的,**没有源码挂载**——改完 Python 代码不重建镜像就跑等于跑旧代码。这是重要陷阱。 +**Docker 边界**:`tushare/src/` 与 `web/backend/`、`web/frontend/` 均在 Dockerfile 的 `COPY` 阶段拷进镜像,**没有源码挂载**——改完 Python/Go/Vue 代码不重建镜像就跑等于跑旧代码。这是重要陷阱。 **Bark 推送**:`notifier.push_bark` 用 `requests.get` 走路径形式(`/{key}/{title}/{body}`),所有片段以 `quote(safe='')` URL 编码,失败仅 `print [WARN]` 不抛错。容器内首发请求有时 DNS 慢导致 15s timeout,内置 1 次重试;主机直连通常 <1s。 @@ -49,13 +64,14 @@ sqlite3 data/futures.db "SELECT ts_code, trade_date, composite, signal FROM scor ## Web 模块(报告浏览端) -`./web/` 是独立的报告浏览端,与 `tushare` 流水线解耦:tushare 写 `data/futures.db`,web 只读访问。docker-compose 上是新增的 `web` 服务,与 `tushare` 共存不互相依赖。 +`./web/` 是独立的报告浏览端,与 `tushare` 流水线解耦:tushare 写 PostgreSQL,web 只读访问业务数据、读写 `auth.db`。docker-compose 上是 `web` 服务,与 `tushare`/`postgres` 共存不互相依赖。 **架构与边界**: -- 后端 Go(`golang:1.25.8-alpine3.23` 构建,`alpine:3.23` 运行),路由 `chi`,SQLite 驱动 `modernc.org/sqlite`(纯 Go 无 CGO,二进制更小、不需要 gcc)。前端 Vue 3 + Vite + Element Plus + ECharts。 +- 后端 Go(`golang:1.25.8-alpine3.23` 构建,`alpine:3.23` 运行),路由 `chi`,业务数据库驱动 `github.com/lib/pq`(PostgreSQL),鉴权数据库仍用 `modernc.org/sqlite`(纯 Go 无 CGO)管理 `auth.db`。前端 Vue 3 + Vite + Element Plus + ECharts。 - 单进程同源服务:Vue 产物在 Docker 构建期由 `node` 阶段产出 `dist/`,被 `go:embed all:dist` 嵌入二进制,运行时由 Go 同时服务 `/api/*` 与 SPA 静态文件——不引入 nginx 旁车。 -- 双 DB 分离:`futures.db` 以 `mode=ro&query_only(true)` 打开,容器挂 `:ro` 双重保险;`auth.db` 由 web 自己 init/写入,落在 `./data/auth.db`(已被 `.gitignore` 覆盖)。 +- 双 DB 分离:业务数据 `futures` (PostgreSQL)通过 `DATABASE_URL` 只读访问;`auth.db`(SQLite)由 web 自己 init/写入,落在 `./data/auth.db`(已被 `.gitignore` 覆盖),容器挂载 `./data:/app/auth`。 - 鉴权 JWT(HS256, Bearer header),12h 过期,无 sessions 表。前端把 token 持久化到 `localStorage`,axios 拦截器统一注入 + 401 自动跳登录。 +- 前端支持暗/浅色模式切换(`stores/theme.ts`),侧边导航在暗色模式用 `#282828`、浅色模式用 `#f9fafb`。 **禁止公开注册**:登录后管理员才能在 `/admin/users` 维护账号。首次启动时 `auth.Bootstrap` 检查 `users` 表,若没有 admin 行 *且* env 同时存在 `ADMIN_USER`/`ADMIN_PASS`,则用 bcrypt(cost=12) 写一行 admin。一旦 admin 存在,这两个 env 被静默忽略——避免轮换 env 时静默改密。忘记管理员密码的恢复方式:停服 → `sqlite3 data/auth.db "DELETE FROM users WHERE role='admin'"` → 重置 env → 重启。 diff --git a/使用说明.md b/README.md similarity index 68% rename from 使用说明.md rename to README.md index 7c364f8..ccbd6f7 100644 --- a/使用说明.md +++ b/README.md @@ -1,12 +1,12 @@ # 期货行情分析系统 — 使用说明 -基于 Docker + Python(tushare) 的中国期货行情分析系统。当前阶段已实现数据采集与三层加权打分模型,运行方式为脚本自动化(宿主机定时器触发 `docker-compose run`)。 +基于 Docker + Python(tushare) + PostgreSQL 的中国期货行情分析系统。当前阶段已实现数据采集、三层加权打分模型、Bark 推送通知与 Web 报表浏览端。运行方式支持两种模式:① 宿主机定时器触发 `docker-compose run` 执行 CLI;② 通过 FastAPI HTTP API 服务触发。 ## 环境准备 - Docker >= 20.10 - Docker Compose >= 2.0 -- (可选) sqlite3 CLI 用于本地查库 +- (可选) psql 或任意 PostgreSQL 客户端用于本地查库 ## 快速开始 @@ -20,21 +20,46 @@ echo "TUSHARE_TOKEN=你的token" > tushare/.env 该文件已被 gitignore 排除,不会进入版本库。 -### 2. 启动并跑当月主力 +### 2. 启动全栈服务 ```bash -docker-compose run --rm tushare +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. 写入 SQLite `data/futures.db` +2. 写入 PostgreSQL `futures` 数据库 3. 运行三层打分模型 4. 保存打分结果并输出到 stdout 5. 通过 Bark 推送评分摘要 -### 3. 跑其他合约或品种 +### 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 # 显式指定合约 @@ -45,7 +70,7 @@ docker-compose run --rm tushare python -m src.main I2601.DCE docker-compose run --rm tushare python -m src.main --symbol FG ``` -### 4. 玻璃 FG 主力轮换规则 +### 6. 玻璃 FG 主力轮换规则 | 当前自然月 | 主力合约 | |----------|---------| @@ -112,44 +137,48 @@ docker-compose run --rm tushare python -m src.main --symbol FG ## 数据查询 -SQLite 数据库位于 `data/futures.db`,可直接用 sqlite3 查询: +业务数据存储在 PostgreSQL 中,可通过以下方式查询: ```bash # 查看最新打分 -sqlite3 data/futures.db "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 ts_code, trade_date, composite, signal FROM scores ORDER BY trade_date DESC LIMIT 5;" # 查看合约日线 -sqlite3 data/futures.db "SELECT trade_date, open, high, low, close, vol, oi FROM candles WHERE ts_code='FG2609.ZCE' ORDER BY trade_date DESC LIMIT 10;" +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;" -# 查看表结构 -sqlite3 data/futures.db ".schema" +# 或通过 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 编排(tushare + web 两个服务) +├── docker-compose.yml # Docker Compose 编排(postgres + tushare + web) ├── 使用说明.md # 本文件 -├── data/ # SQLite 数据库目录(gitignored) -│ ├── futures.db # tushare 写入,web 只读 -│ └── auth.db # web 自己维护的用户表 +├── CLAUDE.md # Claude Code 项目指引 +├── data/ # auth.db 目录(gitignored) +│ └── auth.db # web 自己维护的用户表(SQLite) ├── .gitignore # Git 忽略配置 ├── tushare/ # Python 数据服务 │ ├── Dockerfile │ ├── requirements.txt │ ├── .env # TUSHARE_TOKEN(本地,不入库) -│ └── src/ # 数据采集 + 打分 + Bark 推送 +│ └── src/ # 数据采集 + 打分 + Bark 推送 + FastAPI +│ ├── api.py # FastAPI 服务入口 │ ├── models.py │ ├── fetcher.py │ ├── scorer.py -│ ├── storage.py +│ ├── storage.py # PostgreSQL 读写 │ ├── contracts.py │ ├── notifier.py -│ └── main.py +│ └── main.py # CLI 入口 └── web/ # Web 浏览端 ├── .dockerignore - ├── backend/ # Go 1.25 后端 (chi + modernc.org/sqlite + JWT) + ├── backend/ # Go 1.25 后端 (chi + lib/pq + JWT) │ ├── Dockerfile # 多阶段:node 构 UI → go 构二进制 → alpine 运行 │ ├── go.mod │ ├── main.go @@ -158,7 +187,7 @@ trade/ │ ├── dist/ # 占位,Docker 构建期被 vite 输出覆盖 │ └── internal/ │ ├── config/ # 环境变量加载 - │ ├── store/ # futures.db 只读 + auth.db 用户表 + │ ├── store/ # PostgreSQL 业务查询 + SQLite auth.db │ ├── auth/ # JWT + bcrypt + 首启 admin 引导 │ ├── middleware/ # RequireUser / RequireAdmin / 日志 │ ├── handlers/ # 登录 / 打分 / K线 / 用户管理 @@ -171,7 +200,7 @@ trade/ └── src/ ├── main.ts / App.vue ├── router/ # 守卫(未登录/管理员路由) - ├── stores/auth.ts # Pinia,持久化 token + ├── stores/ # Pinia: auth.ts(持久化 token) + theme.ts(暗/浅色模式) ├── api/ # axios 封装 + 各端点 ├── views/ # 登录 / 打分列表 / 图表 / 用户管理 └── components/ # 抽屉 + ECharts K 线 @@ -179,10 +208,11 @@ trade/ ## 技术栈 -- **Python 3.13** (alpine) + **tushare** + **pandas** — 数据采集与打分 -- **Go 1.25.8** (alpine 3.23) + **chi** + **modernc.org/sqlite** + **JWT** — Web 后端 +- **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 前端 -- **SQLite** — 本地数据存储(双库:`futures.db` 业务 + `auth.db` 鉴权) +- **PostgreSQL 18.3** (alpine 3.23) — 业务数据存储 +- **SQLite** — 鉴权数据存储(auth.db) - **Docker / Docker Compose** — 容器化部署 ## 常见问题 @@ -197,11 +227,11 @@ A: 郑商所用 `.ZCE` 后缀(如 `FG2609.ZCE`),上期所用 `.SHF`,大 **Q: 如何定时自动跑?** -A: 通过宿主机 cron / launchd 等定时器调用 `docker-compose run --rm tushare ...`。打分结束会通过 Bark 推送结果(见 `tushare/src/notifier.py`)。 +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 流水线写入 `data/futures.db` 的打分与行情数据。后端 Go(`golang:1.25.8-alpine3.23`)读取数据库,前端 Vue 3 + Element Plus + ECharts,通过 docker-compose 一起部署。 +`./web/` 提供一个图形化的浏览端,展示 tushare 流水线写入 PostgreSQL 的打分与行情数据。后端 Go(`golang:1.25.8-alpine3.23`)读取数据库,前端 Vue 3 + Element Plus + ECharts,通过 docker-compose 一起部署。 ### 1. 配置首启凭据 @@ -235,6 +265,8 @@ docker-compose logs -f web 普通用户登录后 `/admin/*` 路径会被前端守卫拦截并跳回 `/scores`,后端也会以 403 拒绝。 +前端支持暗/浅色模式切换,点击顶部导航栏的「暗/亮」开关即可切换。侧边导航在暗色模式使用深色背景,浅色模式使用浅色背景。 + ### 4. 子账号维护流程 1. 用 admin 登录 → 进入 `/admin/users` → 「新建账号」,填写用户名 / 密码(≥6 位) / 角色。 @@ -244,17 +276,18 @@ docker-compose logs -f web ### 5. 数据流向与数据库分离 ``` -tushare(写) → data/futures.db ──(只读挂载 :ro)──> web 服务 ←(读写)→ data/auth.db +tushare(写) → PostgreSQL futures 数据库 ←(只读)── web 后端 +web 后端 ←(读写)→ data/auth.db (SQLite) ``` -`futures.db` 的 schema 与 Python 端一致(`candles` + `scores`)。`auth.db` 表为: +业务数据(`candles` + `scores`)统一存储在 PostgreSQL 中,`auth.db` 表为: ```sql users(id, username UNIQUE, password_hash, role IN ('admin','user'), disabled, created_at, updated_at) ``` -两个 DB 都在 `./data/` 目录,均被 `.gitignore` 覆盖。 +`auth.db` 在 `./data/` 目录,被 `.gitignore` 覆盖。 ### 6. 常见问题 @@ -277,6 +310,6 @@ docker-compose up -d web `web/backend/.env` 没设或太短,用 `openssl rand -hex 32` 生成一个 64 字符的十六进制字符串即可。 -**Q: 容器内能不能误写 futures.db?** +**Q: 为什么 tushare 容器启动后没有立即退出?** -不能。容器以 `./data:/app/data:ro` 挂载,Go 又用 `mode=ro&query_only(true)` 打开数据库,双层保险。auth.db 走另一个挂载点 `./data:/app/auth`(同物理目录但路径不同,无 `:ro`)。 +因为默认命令改为 `uvicorn src.api:app` 常驻 API 服务。如需执行单次 CLI,用 `docker-compose run --rm tushare python -m src.main ...`。