# 期货行情分析系统 — 使用说明 基于 Docker + Python(tushare) 的中国期货行情分析系统。当前阶段已实现数据采集与三层加权打分模型,运行方式为脚本自动化(宿主机定时器触发 `docker-compose run`)。 ## 环境准备 - Docker >= 20.10 - Docker Compose >= 2.0 - (可选) sqlite3 CLI 用于本地查库 ## 快速开始 ### 1. 配置 tushare token 将 token 写入 `tushare/.env`: ```bash echo "TUSHARE_TOKEN=你的token" > tushare/.env ``` 该文件已被 gitignore 排除,不会进入版本库。 ### 2. 启动并跑当月主力 ```bash docker-compose run --rm tushare ``` 不传参时,按 `tushare/src/contracts.py` 的 `ROLLOVER_RULES` 自动选 FG 玻璃当月主力(例如 2026-05 -> `FG2609.ZCE`),启动后会先打印 `[AUTO] FG 当月主力 -> ...`,然后: 1. 从 tushare 拉取合约日线数据 2. 写入 SQLite `data/futures.db` 3. 运行三层打分模型 4. 保存打分结果并输出到 stdout 5. 通过 Bark 推送评分摘要 ### 3. 跑其他合约或品种 ```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 ``` ### 4. 玻璃 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 | 强烈看空 — 资金主动打压 | ## 数据查询 SQLite 数据库位于 `data/futures.db`,可直接用 sqlite3 查询: ```bash # 查看最新打分 sqlite3 data/futures.db "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;" # 查看表结构 sqlite3 data/futures.db ".schema" ``` ## 项目结构 ``` trade/ ├── docker-compose.yml # Docker Compose 编排(tushare + web 两个服务) ├── 使用说明.md # 本文件 ├── data/ # SQLite 数据库目录(gitignored) │ ├── futures.db # tushare 写入,web 只读 │ └── auth.db # web 自己维护的用户表 ├── .gitignore # Git 忽略配置 ├── tushare/ # Python 数据服务 │ ├── Dockerfile │ ├── requirements.txt │ ├── .env # TUSHARE_TOKEN(本地,不入库) │ └── src/ # 数据采集 + 打分 + Bark 推送 │ ├── models.py │ ├── fetcher.py │ ├── scorer.py │ ├── storage.py │ ├── contracts.py │ ├── notifier.py │ └── main.py └── web/ # Web 浏览端 ├── .dockerignore ├── backend/ # Go 1.25 后端 (chi + modernc.org/sqlite + 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/ # futures.db 只读 + 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/auth.ts # Pinia,持久化 token ├── api/ # axios 封装 + 各端点 ├── views/ # 登录 / 打分列表 / 图表 / 用户管理 └── components/ # 抽屉 + ECharts K 线 ``` ## 技术栈 - **Python 3.13** (alpine) + **tushare** + **pandas** — 数据采集与打分 - **Go 1.25.8** (alpine 3.23) + **chi** + **modernc.org/sqlite** + **JWT** — Web 后端 - **Vue 3** + **Vite** + **Element Plus** + **ECharts** — Web 前端 - **SQLite** — 本地数据存储(双库:`futures.db` 业务 + `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`)。 ## Web 报表(浏览端) `./web/` 提供一个图形化的浏览端,展示 tushare 流水线写入 `data/futures.db` 的打分与行情数据。后端 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(写) → data/futures.db ──(只读挂载 :ro)──> web 服务 ←(读写)→ data/auth.db ``` `futures.db` 的 schema 与 Python 端一致(`candles` + `scores`)。`auth.db` 表为: ```sql users(id, username UNIQUE, password_hash, role IN ('admin','user'), disabled, created_at, updated_at) ``` 两个 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: 容器内能不能误写 futures.db?** 不能。容器以 `./data:/app/data:ro` 挂载,Go 又用 `mode=ro&query_only(true)` 打开数据库,双层保险。auth.db 走另一个挂载点 `./data:/app/auth`(同物理目录但路径不同,无 `:ro`)。