新增主力合约自动选取并补全项目文档
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
46
CLAUDE.md
Normal file
46
CLAUDE.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
基于 Docker + Python(tushare) 的中国期货行情分析系统,实现日线数据采集、三层加权打分模型与 Bark 推送通知。运行方式定位为脚本自动化(宿主机 cron/launchd 等定时调用 `docker-compose run`),不规划独立后端服务。详细业务说明见 `使用说明.md`。
|
||||||
|
|
||||||
|
## 常用命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 不传参 = 按当月 FG 主力自动选合约(轮换规则见 contracts.py:ROLLOVER_RULES)
|
||||||
|
docker-compose run --rm tushare
|
||||||
|
|
||||||
|
# 显式指定合约(注意交易所后缀:.ZCE/.SHF/.DCE,郑商所是 .ZCE 不是 .CZC)
|
||||||
|
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
|
||||||
|
|
||||||
|
# 查最新打分
|
||||||
|
sqlite3 data/futures.db "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。
|
||||||
|
|
||||||
|
## 关键架构
|
||||||
|
|
||||||
|
**单进程串行流水线**:`src.main.main()` 先按命令行参数(显式 `ts_code` 优先,否则 `contracts.active_contract(symbol)` 按当月主力自动选)定下合约,再调 `run()` 顺序执行 `fetcher → storage(candles) → scorer → storage(scores) → notifier`。无后台任务、无队列,每次 CLI 调用处理一个合约一日。
|
||||||
|
|
||||||
|
**主力轮换规则**(`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()` 中维护,新增字段需同步该函数。
|
||||||
|
|
||||||
|
**Docker 边界**:`docker-compose.yml` 仅把 `./data` 挂为 `/app/data`(数据持久化);`tushare/src/` 是在 Dockerfile 的 `COPY --chown=app:app src ./src` 阶段拷进镜像的,**没有源码挂载**——改完 Python 代码不重建镜像就跑等于跑旧代码。这是重要陷阱。
|
||||||
|
|
||||||
|
**Bark 推送**:`notifier.push_bark` 用 `requests.get` 走路径形式(`/{key}/{title}/{body}`),所有片段以 `quote(safe='')` URL 编码,失败仅 `print [WARN]` 不抛错。容器内首发请求有时 DNS 慢导致 15s timeout,内置 1 次重试;主机直连通常 <1s。
|
||||||
|
|
||||||
|
## 配置/密钥规则
|
||||||
|
|
||||||
|
`.gitignore` 排除范围广(见文件):`data/`、`*.db*`、`.env*`、CTP 流文件(`*.con`/`*.dat`/`ResultInfo.xml` 等)、`.claude/`、所有日志。新增任何账户、token、行情流文件务必先确认匹配 ignore 规则。
|
||||||
@@ -6,4 +6,4 @@ services:
|
|||||||
- DB_PATH=/app/data/futures.db
|
- DB_PATH=/app/data/futures.db
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
command: ["python", "-m", "src.main", "FG2609.ZCE"]
|
command: ["python", "-m", "src.main"]
|
||||||
|
|||||||
@@ -25,4 +25,4 @@ RUN adduser -D -u 1000 app \
|
|||||||
COPY --chown=app:app src ./src
|
COPY --chown=app:app src ./src
|
||||||
USER app
|
USER app
|
||||||
|
|
||||||
CMD ["python", "-m", "src.main", "FG2609.ZCE"]
|
CMD ["python", "-m", "src.main"]
|
||||||
|
|||||||
31
tushare/src/contracts.py
Normal file
31
tushare/src/contracts.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from datetime import date
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
# 品种主力合约轮换规则。
|
||||||
|
# 每个品种维护:
|
||||||
|
# exchange: tushare 合约后缀(交易所)
|
||||||
|
# active: 当月 -> (主力合约月, 年份偏移)
|
||||||
|
# 例 FG 12 月用次年 5 月,故 12 -> (5, 1)
|
||||||
|
ROLLOVER_RULES: dict[str, dict] = {
|
||||||
|
"FG": {
|
||||||
|
"exchange": "ZCE",
|
||||||
|
"active": {
|
||||||
|
1: (5, 0), 2: (5, 0), 3: (5, 0),
|
||||||
|
4: (9, 0), 5: (9, 0), 6: (9, 0), 7: (9, 0),
|
||||||
|
8: (1, 1), 9: (1, 1), 10: (1, 1), 11: (1, 1),
|
||||||
|
12: (5, 1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def active_contract(symbol: str, today: Optional[date] = None) -> str:
|
||||||
|
"""按主力轮换规则,返回当日 ts_code(含交易所后缀)。"""
|
||||||
|
if symbol not in ROLLOVER_RULES:
|
||||||
|
raise ValueError(f"未配置 {symbol} 的主力轮换规则,可在 contracts.ROLLOVER_RULES 中追加")
|
||||||
|
|
||||||
|
today = today or date.today()
|
||||||
|
rule = ROLLOVER_RULES[symbol]
|
||||||
|
contract_month, year_offset = rule["active"][today.month]
|
||||||
|
year = today.year + year_offset
|
||||||
|
return f"{symbol}{year % 100:02d}{contract_month:02d}.{rule['exchange']}"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from . import fetcher, notifier, scorer, storage
|
from . import contracts, fetcher, notifier, scorer, storage
|
||||||
|
|
||||||
|
|
||||||
def run(ts_code: str) -> int:
|
def run(ts_code: str) -> int:
|
||||||
@@ -76,9 +76,22 @@ def run(ts_code: str) -> int:
|
|||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(description="期货合约三层打分模型")
|
parser = argparse.ArgumentParser(description="期货合约三层打分模型")
|
||||||
parser.add_argument("ts_code", help="合约代码,如 FG2609.ZCE")
|
parser.add_argument(
|
||||||
|
"ts_code",
|
||||||
|
nargs="?",
|
||||||
|
help="合约代码,如 FG2609.ZCE;不传则按 --symbol 当月主力自动选取",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--symbol",
|
||||||
|
default="FG",
|
||||||
|
help="品种代号,在未传 ts_code 时按 contracts.ROLLOVER_RULES 选当月主力,默认 FG",
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
return run(args.ts_code)
|
|
||||||
|
ts_code = args.ts_code or contracts.active_contract(args.symbol)
|
||||||
|
if not args.ts_code:
|
||||||
|
print(f"[AUTO] {args.symbol} 当月主力 -> {ts_code}")
|
||||||
|
return run(ts_code)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
34
使用说明.md
34
使用说明.md
@@ -1,6 +1,6 @@
|
|||||||
# 期货行情分析系统 — 使用说明
|
# 期货行情分析系统 — 使用说明
|
||||||
|
|
||||||
基于 Docker + Python(tushare) + Go 的中国期货行情分析系统。当前阶段已实现数据采集与三层加权打分模型。
|
基于 Docker + Python(tushare) 的中国期货行情分析系统。当前阶段已实现数据采集与三层加权打分模型,运行方式为脚本自动化(宿主机定时器触发 `docker-compose run`)。
|
||||||
|
|
||||||
## 环境准备
|
## 环境准备
|
||||||
|
|
||||||
@@ -20,28 +20,40 @@ echo "TUSHARE_TOKEN=你的token" > tushare/.env
|
|||||||
|
|
||||||
该文件已被 gitignore 排除,不会进入版本库。
|
该文件已被 gitignore 排除,不会进入版本库。
|
||||||
|
|
||||||
### 2. 启动并跑默认合约
|
### 2. 启动并跑当月主力
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose run --rm tushare
|
docker-compose run --rm tushare
|
||||||
```
|
```
|
||||||
|
|
||||||
默认执行 `FG2609.ZCE`(玻璃期货 2609 合约),流程:
|
不传参时,按 `tushare/src/contracts.py` 的 `ROLLOVER_RULES` 自动选 FG 玻璃当月主力(例如 2026-05 -> `FG2609.ZCE`),启动后会先打印 `[AUTO] FG 当月主力 -> ...`,然后:
|
||||||
|
|
||||||
1. 从 tushare 拉取合约日线数据
|
1. 从 tushare 拉取合约日线数据
|
||||||
2. 写入 SQLite `data/futures.db`
|
2. 写入 SQLite `data/futures.db`
|
||||||
3. 运行三层打分模型
|
3. 运行三层打分模型
|
||||||
4. 保存打分结果并输出到 stdout
|
4. 保存打分结果并输出到 stdout
|
||||||
|
5. 通过 Bark 推送评分摘要
|
||||||
|
|
||||||
### 3. 跑其他合约
|
### 3. 跑其他合约或品种
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 螺纹钢 2510 合约(上期所)
|
# 显式指定合约
|
||||||
docker-compose run --rm tushare python -m src.main RB2510.SHF
|
docker-compose run --rm tushare python -m src.main RB2510.SHF
|
||||||
|
|
||||||
# 铁矿石 2601 合约(大商所)
|
|
||||||
docker-compose run --rm tushare python -m src.main I2601.DCE
|
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 |
|
||||||
|
|
||||||
## 三层打分模型
|
## 三层打分模型
|
||||||
|
|
||||||
### 综合分数公式
|
### 综合分数公式
|
||||||
@@ -131,6 +143,8 @@ trade/
|
|||||||
├── fetcher.py # tushare 数据拉取
|
├── fetcher.py # tushare 数据拉取
|
||||||
├── scorer.py # 打分模型核心
|
├── scorer.py # 打分模型核心
|
||||||
├── storage.py # SQLite 持久化
|
├── storage.py # SQLite 持久化
|
||||||
|
├── contracts.py # 主力合约轮换规则
|
||||||
|
├── notifier.py # Bark 推送
|
||||||
└── main.py # CLI 入口
|
└── main.py # CLI 入口
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -154,8 +168,4 @@ A: 郑商所用 `.ZCE` 后缀(如 `FG2609.ZCE`),上期所用 `.SHF`,大
|
|||||||
|
|
||||||
**Q: 如何定时自动跑?**
|
**Q: 如何定时自动跑?**
|
||||||
|
|
||||||
A: 当前为手动 CLI 触发。后续可在 `docker-compose.yml` 中增加 cron 服务或接入调度器。
|
A: 通过宿主机 cron / launchd 等定时器调用 `docker-compose run --rm tushare ...`。打分结束会通过 Bark 推送结果(见 `tushare/src/notifier.py`)。
|
||||||
|
|
||||||
**Q: Go 后端怎么读数据?**
|
|
||||||
|
|
||||||
A: Go 端可直接用 `database/sql` + `github.com/mattn/go-sqlite3` 读取 `data/futures.db` 中的 `candles` 和 `scores` 表。
|
|
||||||
|
|||||||
Reference in New Issue
Block a user