支持一键预测脚本

This commit is contained in:
2026-08-23 14:30:20 +08:00
parent dc65b98568
commit 286626dfe5
8 changed files with 561 additions and 38 deletions

125
docs/model.md Normal file
View File

@@ -0,0 +1,125 @@
# 模型与评估
本文描述当前默认产物 [`artifacts/v1/model.joblib`](../artifacts/v1/model.joblib),不是所有历史实验的合集。
## 当前模型
| 项目 | 当前值 |
| --- | --- |
| 模型版本 | `v1` 目录,序列化版本 `4` |
| 特征版本 | `compact6` |
| 时间周期 | 8H |
| 输入窗口 | 最近 18 根已收盘 8H K 线 |
| 预测窗口 | 后续 6 根 8H K 线 |
| 点模型 | `ExtraTreesRegressor`,平方误差 |
| 分位数模型 | 两个 `HistGradientBoostingRegressor`,q20/q80 |
| 特征数 | 146 |
| 当前 ATR 过滤阈值 | `0.0`,不主动过滤样本 |
正式模型文件、训练报告和机器可读指标分别是:
```text
artifacts/v1/model.joblib
artifacts/v1/report.md
artifacts/v1/report.json
```
## 目标定义
对时间点 `t`,先取未来 6 根 K 线的最低价,再取这些最低价的 20% 分位数作为稳健低点参考:
```text
future_low_reference = quantile(low[t+1:t+6], 0.20)
raw_return = future_low_reference / close[t] - 1
target = raw_return / asset_atr_price_top20_median
```
这不是单根最低影线的 `lowestLow`。使用 20% 分位数可以降低一根异常影线对标签的支配,同时保留上涨阶段未来低点高于当前收盘的正收益。负 `raw_return` 表示未来稳健低点低于当前收盘,正值表示它高于当前收盘。
`asset_atr_price_top20_median` 是该品种训练期 `ATR / close` 按降序取前 20% 后的中位数。模型在训练时预测归一化目标,输出时再乘回该品种尺度,使不同价格和波动水平的品种可以由同一个模型处理。
## 特征
当前 `compact6` 由 `18 × (6 + 2) + 2 = 146` 个特征组成:
### 最近 18 根 K 线形态
每根 K 线保留 6 个无量纲或相对特征:
- `body_range`:有符号实体相对 K 线振幅;
- `upper_wick`、`lower_wick`:上下影线相对振幅;
- `close_location`:收盘价在本根高低区间中的位置;
- `range_pct`:振幅相对收盘价;
- `log_return`:对数收益率。
没有把绝对价格直接输入模型,也没有在 `compact6` 中重复保存实体绝对大小或独立的阳阴线标记。
### 最近 18 根成交量
- `quote_volume_rank`:本根 USDT 成交额在最近 18 根中的百分位排名;
- `quote_volume_log_rel_median`:本根 USDT 成交额对最近 18 根中位数的对数相对值。
这里使用 `quote` 成交额,而不是不同币种单位不可比的原始成交量。
### 品种画像
每个品种只保留两个训练期画像值,并在训练品种之间标准化:
- 高波动 20% 样本的 `ATR / close` 中位数;
- 高成交额 20% 样本的 `log1p(quote)` 中位数。
RSI、SMA、布林带、EMA、趋势汇总、全局市场摘要和横截面排名都做过候选实验,但当前正式模型没有使用它们。全局和横截面特征还会改变预测所需的上下文,不能直接用于当前的单 CSV 预测接口。
## 训练协议
默认使用最近 36 个月数据,最后 10 个月作为留出期。训练快照以前的样本只能使用完整的历史窗口和未来 6 根 K 线;跨越测试边界或数据缺口的样本会被丢弃。
品种先按固定随机种子 `42` 拆分,约 60% 作为训练品种、20% 作为验证品种、20% 作为测试品种。最近 10 个月再按时间拆成:
- 验证品种的前 30%:模型和特征选择;
- 验证品种的接续 20%:分位数校准;
- 测试品种的最后 50%:最终测试,只报告一次。
每个阶段会比较随机森林和 ExtraTrees 点模型,并使用 q20/q80 分位数模型。特征阶段和候选模型只根据验证选择分数确定,最终测试集不参与选择。
## 测试结果
当前报告的最终测试集包含 13,950 个样本。误差是在乘回品种 ATR 尺度后的原始收益空间中计算:
| 指标 | `compact6` |
| --- | ---: |
| 点预测 MAE | `0.024918` |
| 点预测 RMSE | `0.033563` |
| q80 覆盖率 | `77.50%` |
| q20 下界命中率 | `91.60%` |
| q20 覆盖率(`y <= q20`) | `8.40%` |
相同复核配置下,182 特征基线的点预测 MAE/RMSE 是 `0.025140 / 0.033789`,`compact6` 分别改善约 `0.88% / 0.67%`。q80 的实测覆盖率是测试集统计结果,不是未来每次预测的保证概率;`80%` 也不能被理解为盈利概率或交易成功率。
q20 的两个指标不要混淆:`q20_coverage` 统计真实目标低于 q20 预测的比例,`q20_hit_rate` 统计真实目标高于 q20 预测的比例,所以二者在没有相等值时大致互补。
## 预测字段语义
模型内部预测的是 signed future-low return,批量 CSV 将其转成更直观的字段:
```text
drawdown = -future_low_return × 100
price_level = close × (1 + future_low_return)
```
因此:
- `drawdown_point_pct` 是点预测;
- `drawdown_cap_80pct_pct` 使用 q80 收益,对应较浅的回撤上界参考;
- `price_level_reached_80pct` 使用 q20 收益,对应较保守的较低价格参考;
- `price_level_cap_80pct` 使用 q80 收益,对应较高的价格参考。
当未来稳健低点预测为正收益时,回撤列可以为负,这表示模型没有把上涨阶段强行截断为零。
## 使用限制
- 这是离线历史数据上的统一回归模型,不是交易信号,也不保证未来表现。
- 测试结果依赖当前数据快照、品种筛选和时间切分;更新数据后重新训练,指标可能变化。
- 模型只对保存了训练画像且有足够历史的品种预测。当前 ATR 排名至少需要 200 根 8H K 线。
- `atr_rank_200_pct` 是状态描述,不代表模型只预测高波动时刻;正式阈值为 `0.0`。
- 预测结果中的空值表示当前时刻不满足模型资格条件,不应替换成零。

134
docs/usage.md Normal file
View File

@@ -0,0 +1,134 @@
# 使用与数据流
本文是当前代码的运行说明。历史特征实验不在这里维护;它们只用于追溯模型选择过程。
## 依赖
运行需要:
- Python 3 和本项目的虚拟环境;
- `pandas`、`numpy`、`scikit-learn`、`joblib` 等项目依赖;
- Go 和 `/home/ctyun/ban/banbot` 源码;
- banbot 配置中的 QuestDB 数据库,以及对 Binance 数据的访问。
默认配置文件是 `/home/ctyun/ban/banbot/biz/config.yml`。如果路径不同,可通过 `--banbot-config` 和 `--banbot-dir` 覆盖。也可以直接传入 `--dsn`。
安装:
```bash
python3 -m venv .venv
.venv/bin/pip install -e .
```
## 一键更新与预测
```bash
./scripts/update_and_predict.sh
```
脚本调用 `python -m cbpred.latest_predict`,按以下顺序执行:
1. 检查配置中的 QuestDB 是否可连接;QuestDB 默认不可用时,通过 banbot 的导出命令触发其启动流程。
2. 从数据库筛选 Binance `linear` 市场、结算币为 USDT、上市至少 730 天且未退市的品种。
3. 调用 banbot 下载最近 36 个月的 1H K 线。
4. 调用 banbot 导出原始 K 线,并只保留连续完整的 8 根 1H K 线聚合成 8H K 线。
5. 写入 `data/manifest.json` 和 `data/raw/*.csv`。
6. 加载 `artifacts/v1/model.joblib`,对清单中的每个品种使用最新完整 8H K 线预测。
7. 按最新 8H USDT 成交额降序写入 `predictions/latest_v1/predictions.csv`,并写入 `report.json`。
脚本的默认参数可以覆盖:
```bash
./scripts/update_and_predict.sh \
--banbot-dir /path/to/banbot \
--banbot-config /path/to/config.yml \
--data-dir data \
--artifact artifacts/v1/model.joblib \
--output-dir predictions/latest_v1 \
--end 2026-08-23T00:00:00Z
```
`--end` 用于固定数据快照,支持 UTC 日期、ISO 时间或毫秒时间戳。默认使用当前 UTC 时间向下取整到 8H 边界。
## 手动命令
### 准备数据
```bash
.venv/bin/python -m cbpred prepare \
--banbot-config /home/ctyun/ban/banbot/biz/config.yml \
--out-dir data
```
只使用已经下载的数据:
```bash
.venv/bin/python -m cbpred prepare \
--banbot-config /home/ctyun/ban/banbot/biz/config.yml \
--out-dir data \
--skip-download
```
`prepare` 的默认范围是最近 36 个月,训练快照前的品种至少上市 730 天,最近 10 个月预留给测试。`manifest.json` 记录筛选出的品种、数据范围、数据库类型和被排除的原因。
### 训练模型
```bash
.venv/bin/python -m cbpred train \
--data-dir data \
--artifact-dir artifacts/v1
```
训练会输出阶段和候选模型进度到 stderr,并在 `artifacts/v1/` 写入:
- `model.joblib`:当前可供预测的模型;
- `report.json`:机器可读的训练协议和评估指标;
- `report.md`:人类可读的评估摘要。
`--volatility-rank-threshold` 可以做 ATR 排名过滤实验。正式 `v1` 模型的阈值是 `0.0`,因此不会过滤低波动样本;`atr_rank_200_pct` 仍会作为当前状态字段输出。
### 单品种预测
```bash
.venv/bin/python -m cbpred predict \
--artifact artifacts/v1/model.joblib \
--csv data/raw/123_BTC_USDT_USDT.csv \
--symbol BTC/USDT:USDT
```
不传 `--at` 时使用 CSV 中最新完整 8H K 线。传入 `--at` 时,时间必须精确匹配一个 8H K 线起始时间,且不能早于模型训练快照。
模型需要至少 200 根 8H K 线计算 ATR 排名,并需要模型中保存的该品种训练画像。`eligible_for_model` 为 `false` 时,预测数值返回空值,并包含 `skip_reason`。
## 文件布局
```text
cbpred/
├── cbpred/ # Python CLI、特征和训练/预测管线
├── scripts/update_and_predict.sh # 一键入口
├── data/
│ ├── manifest.json # 数据快照和品种清单
│ └── raw/*.csv # 导出的 8H OHLCV
├── artifacts/
│ └── v1/ # 唯一正式模型版本
└── predictions/
└── latest_v1/ # 最近一次批量预测
```
模型版本目录使用 `v1` 形式维护。历史实验可以保留在文档或实验输出目录中,但不应作为默认预测模型或在项目根目录新增多个 `artifacts_*` 入口。
## 故障排查
- `missing virtualenv Python`:先执行安装命令,确认 `.venv/bin/python` 存在。
- QuestDB 连接失败:检查 banbot 配置中的数据库地址、QuestDB 服务和端口;一键脚本只对默认 QuestDB 端口尝试触发 banbot 启动。
- `missing data/manifest.json`:先运行 `prepare`,或确认一键脚本没有在下载/导出阶段失败。
- 某品种预测为空:检查 `eligible_for_model` 和 `skip_reason`,通常是 200 根 ATR 历史不足或该品种没有正式模型画像。
- 数据只有部分 8H K 线:导出会丢弃不连续或不完整的 8 根 1H 桶,这是为了避免特征和未来标签跨越缺口。
## 验证
```bash
./scripts/update_and_predict.sh --help
.venv/bin/python -m unittest discover -q
git diff --check
```

View File

@@ -1,4 +1,6 @@
> 历史实验记录:本文保留 v0 阶段的特征分析。当前运行入口、正式模型和最新指标以 [`README.md`](../README.md)、[`usage.md`](usage.md) 和 [`model.md`](model.md) 为准。
特征 维度 是否启用 介绍
───────────────────────────── ─────────── ────────────── ─────────────────────────────────────────
最近18个K线形态特征 144 是
@@ -73,4 +75,4 @@
- 品种固定画像:约 0.7%
这说明模型主要依赖最近18根K线的形态和波动结构,成交量有辅助作用,长期品种画像目前影响较小。这个重要性
只代表 ExtraTrees 点模型的特征分裂贡献,不等同于因果关系。
只代表 ExtraTrees 点模型的特征分裂贡献,不等同于因果关系。

View File

@@ -1,3 +1,5 @@
> 历史实验记录:本文保留 v1 特征复核过程。当前运行入口、正式模型和最新指标以 [`README.md`](../README.md)、[`usage.md`](usage.md) 和 [`model.md`](model.md) 为准。
我考虑下进行下面的尝试:
1. 每个K线提取8个信息,其中冗余的不少,尝试压缩下;只要下降不是很多就可以保留。