Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ venv/
.env

# Data artifacts (quant: keep large data out of git)
artifacts/
*.parquet
*.feather
*.h5
Expand Down
70 changes: 70 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Quantitative_Trading — A股截面多因子框架

## 项目定位
- A股 **截面多因子选股**(cross-sectional multi-factor)框架。**不做择时**。
- 路径:研究/回测优先 → 渐进到实盘。
- 构建方式:成熟工具 + **自建因子层**为核心。
- 完整架构设计见 [`tmp/framework/architecture.html`](tmp/framework/architecture.html)。本文件是精简操作版,**细节以该文档为准**。

## 架构:7 层
```
data → universe → factors(特征) → alpha(合成/预测) → portfolio(+risk约束) → runtime(backtest|live) → analytics
```

| 层 | 职责 |
|---|---|
| `data/` | 采集(feed) / 清洗复权·对齐披露日(clean) / 面板存储(store) |
| `universe/` | PIT 成分股 + 可交易过滤(停牌/涨跌停/ST) |
| `factors/` | 截面因子 计算(compute) / 预处理三件套(process) / 落盘(store) |
| `alpha/` | 多因子合成/预测(独立成层) |
| `portfolio/` | 组合构建 + 风控**事前约束**(construct.py / risk.py) |
| `runtime/` | 回测 与 实盘 统一:driver + execution,两套实现(backtest/live) |
| `analytics/` | alphalens(因子检验) + quantstats(组合绩效) |

## 不可违反的设计不变量(写代码必须守)
1. **factors 永不碰未来收益**;只有 `alpha` 层才用未来收益拟合权重(防未来函数边界)。
2. **回测即实盘**:`runtime` 的 backtest/live 是同接口两实现;`factors/alpha/portfolio` 层两边复用、一行不改。
3. **分层解耦**:factor 不碰数据源;portfolio 不碰下单。坏味道:策略里直接 `pro.daily(...)`。
4. 决策是**截面的**(每个调仓日横向排序选股);时序只用于算因子。

## 致命陷阱(correctness 红线,详见 architecture.html §8)
未来函数 · PIT 成分股 · 可交易过滤 · 财务按 `ann_date` 披露日对齐 · 前复权 · batch≡incremental 一致性 · 行业+市值中性化 · 交易成本/换手 · 过拟合(样本内外+IC稳定性)。

## 环境
- **框架/测试环境**:conda `quant_mf`(Py 3.12)。运行 Phase 0、pytest、ruff、CLI 时用绝对路径 python:
`/home/shaofl/Development/env_tools/envs/quant_mf/bin/python`
- **数据拉取环境**:conda `data_fetch`(Py 3.12)。仅用于独立数据抓取/交互查数;在非交互 shell 里**用绝对路径 python**,不靠 activate:
`/home/shaofl/Development/env_tools/envs/data_fetch/bin/python`

## 数据:tushare
- **token**:`/home/shaofl/Projects/financial_projects/.config.json`(key `tushare.token`)。
⚠️ **绝不打印、绝不写进 repo、绝不 commit。** 代码里从该文件读取,不硬编码。
- **权限**:实测充足——个股日线 / 分钟(`stk_mins`) / 复权(`adj_factor`) / 成分股(`index_weight`) / 申万行业(`index_classify`) / 财务含`ann_date`(`income`,`fina_indicator`) 全可取。分钟级可直接上,无需先退回日线。
- **MCP(可选开发工具)**:`financial_projects/.mcp.json` 有 tushare MCP,仅供开发期交互查数;**从 `financial_projects/` 启动 Codex 才加载**。
- **数据层 ETL 一律用 Python SDK(批量/增量),不要建在 MCP 上。** 注意 tushare 各接口有每分钟调用上限,批量拉取需限流+重试。

## 技术选型
| 用途 | 选型 |
|---|---|
| 数据处理 | pandas(+ polars 可选)· numpy |
| 存储 | parquet(分钟级按 symbol/year 分区)· DuckDB |
| 因子检验 | alphalens-reloaded |
| 绩效 | quantstats |
| 回归/合成 | statsmodels · scikit-learn |
| 组合优化(后期) | cvxpy · riskfolio-lib |
| 实盘下单(后期) | vnpy / miniqmt(QMT) —— **仅作下单通道,不当回测引擎** |
| 配置/测试 | pydantic-settings + YAML · pytest |

## 开发约定
- **交流中文**;代码/注释/commit message 用**英文**。
- **Git**:feature 分支 + PR。main 已有骨架;当前在 `data` 分支搭数据层。commit 用 conventional 格式,**无 attribution**(不加 Co-Authored-By)。
- **不过度设计**:按路线图 MVP 先打通一条端到端链路,再加层(architecture.html §11,Phase 0→3)。
- **secrets** 一律走外部 `.config.json`;repo `.gitignore` 已排除数据产物(`*.parquet`等)、缓存、`tmp/`(仅留架构文档)。
- 文件小而专(<800 行),immutable 优先。

## 当前进度
- ✅ 7 层骨架 + 架构文档(已在 `main`)
- ✅ Phase 0 MVP(`data` 分支,未提交):DemoFeed → PanelStore → StaticUniverse → momentum_20 → zscore → EqualWeightAlpha → TopN 等权 → 月度回测(成本/换手)→ IC/绩效报告。
- ✅ 质量门:`python -m pytest` 93 passed;`python -m ruff check .` clean;`python -m qt.cli run-phase0 --config config/example.yaml` 可复现。
- ⚠️ P0 显式降级:静态 universe 非 PIT、简版 IC/绩效未走 alphalens/quantstats、`min_listing_days` 配置未生效、持有期末缺价按 0% 结算、tushare 路径接入但未实网验证。
- 下一步 P1:接真 tushare 日线/复权/PIT 成分/可交易过滤,然后再扩因子和中性化。
59 changes: 59 additions & 0 deletions BIAS_AUDIT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Bias Audit (Phase 0)

本文件记录 P0 框架对各类偏差/未来函数的处理状态与降级。每个小节标注当前状态(已处理 / 降级 / 待办)。

## 未来函数 / lookahead

- 状态: **已处理(P0)**。
- `momentum_20[t] = close[t] / close[t-window] - 1`,严格只用 t 及之前的收盘价(`groupby(symbol).shift(window)`)。
- 事件顺序固定:在 t 收盘计算因子,t 收盘后调仓,从 t+1 持有。回测用**下一持有期**的收益结算,绝不使用因子已经看见的当日收益。
- forward returns 只在 `analytics/` 计算,因子层永远拿不到未来收益(INV-001)。

## PIT 成分股

- 状态: **PIT 已实现(P1) / StaticUniverse 为离线降级**。
- `PITIndexUniverse`(`universe.type=index`)用 tushare `index_weight` 的历史快照做 as-of 成分:`members(date)` 取 ≤date 的最近快照,绝不用未来快照(UNI-009)。被剔除的票在其在册期内仍是成员 —— 无幸存者偏差、无成分前视。
- pipeline 构建 index universe 时会额外向回看 370 天成分快照,确保回测从两次成分调整中间开始时,起始日也能取到“开始日前最近快照”,而不是错误空仓。
- 实证:沪深300 2024 全年 24 个快照、328 个不同成分(每快照 300),28 进28 出;`000069.SZ` 在 06-03 在册、06-28 已剔,各按其时代正确归属。
- 数据坑:`index_weight` 单次约 6000 行上限,长窗口会**静默丢最早快照**;feed 已分 90 天窗口分页拉取规避。
- `StaticUniverse`(`universe.type=static`,demo/离线用)成分与日期无关(UNI-003),是**降级**:存在幸存者 / 成分前视偏差,仅供无网络的 demo 跑通,并在 `phase0_summary.md` 的 DOWNGRADES 小节显式记录。

## 可交易过滤

- 状态: **停牌 / ST / 涨跌停已实现(P1)**。
- `missing_close`(总是开):截面日 `close` 为 NaN 的标的不可交易(UNI-004)。
- 统一在 `universe.filters.apply_tradable_filters` 按 `UniverseFilters` 开关执行;flag 由 `data.clean.tradability.enrich_tradability` 从 tushare `suspend_d` / `namechange` / `stk_limit` 富化到 panel(StaticUniverse 与 PITIndexUniverse 共用)。demo 无 flag 数据时各过滤自动 no-op。
- **ST(UNI-006)**:`namechange` 名称区间含 'ST'/'*ST' 即标记,按 date 取生效名称(实证:`000005.SZ` 2024 全程 ST,正确剔除)。
- **涨跌停(UNI-007)**:用**未复权 raw close** 与当日 raw `up_limit`/`down_limit` 比较,标记 `at_up_limit`/`at_down_limit`(qfq 复权价仅用于因子/回测收益;flag 富化在 front_adjust **之前**完成,故比较的是同口径 raw 价)。实证:`000005.SZ` 2024-02-01 触跌停。当前选股层对两个方向都剔除;**方向感知**(买入只看涨停、持有跌停不强卖)属执行层,后续细化。
- **停牌(UNI-005)**:`suspend_d` 标记停牌日。**实测发现**:tushare 全天停牌当日**无 bar** → 已被 `missing_close` 剔除,故显式 suspended flag 与之重叠;其价值在盘中停牌(`suspend_timing`)或会给停牌日 bar 的数据源,属防御性。
- 退市 / 无数据标的(如 `000003.SZ`)同样表现为不在 panel 而被剔除。PIT 历史成分见上节。
- `universe.min_listing_days` 已在配置中(默认 60),但仍 **未执行**(no-op,降级):新上市标的不会被剔除。显式披露(INV-007),后续接上市日期后强制。

## ann_date 财务对齐

- 状态: **已实现(P1)**。
- 财务因子(`roe` / `netprofit_yoy`)经 `data.clean.pit_financials.asof_financials` 按披露日 `ann_date` 做 backward as-of 对齐:每个 trade_date 只取 `ann_date <= trade_date` 的最近一期报告,**绝不按 `end_date`(报告期末)join**(DATA-012)。
- 拉取窗口向回看约 16 个月(`start` 之前),确保回测 `start` 前已披露的上一期财报在集合内、能 as-of **carry forward** 到早期交易日,避免早期 NaN 缺口。
- 实证:平安银行 2024 Q1(end_date 2024-03-31)披露日 ann_date 2024-04-20;as-of roe 在 04-19 仍是上一期年报值(10.2436),04-22 才切到 Q1(3.1176)——晚于报告期末约 3 周,证明无未来披露泄漏。
- 财务因子仅在 tushare 数据路径可用;demo 无披露日,配置财务因子 + demo 源会报可读错误,**不伪造财务**。

## 复权

- 状态: **前复权已实现(P1)**。
- panel 始终携带 `adj_factor` 列(DemoFeed 中恒为 1.0)。`data/clean/adjust.py` 的 `front_adjust` 用 `adj_factor` 做前复权(qfq),在 pipeline 读盘后、因子计算前于内存中应用(DATA-003)。
- 约定:按 symbol 锚定窗口内最新日 (`qfq = raw × adj_factor / adj_factor[latest]`)。锚定项在任何价格比值中约掉,故所有收益率 / 因子值对锚定与扩窗都不变 —— PanelStore 保持 raw(+adj_factor),复权在内存做,batch≡incremental 一致。
- 实证:平安银行 2024-06-14 除权,raw 当日 -5.74%(分红跳空),qfq +0.99%(真实涨跌);momentum_20 因此最多变动 6.77pp。demo(adj=1.0)下为恒等。

## 交易成本

- 状态: **已处理(P0)**。
- 成本 = L1 换手 × `fee_rate`;`turnover = sum(|target_w - current_w|)`,在 symbol 并集上对齐计算。
- 每个调仓期 `net_return = gross_return - cost`,成本拖累在`phase0_summary.md` 中汇总(BT-004)。slippage 参数已预留。
- **结算价缺失约定(P0 降级)**:若持仓标的在持有期末(end)的 `close` 为 NaN(停牌 / 缺数据),回测以 0.0(持平)记其该期收益,而非剔除或用最近可得价结算。该约定在此显式披露(INV-007);P1 接入真实停牌/退市处理后改进结算逻辑。

## 中性化

- 状态: **行业 + 市值中性化已实现(P1)**。
- `factors.process.neutralize.neutralize_by_date`:每个 date 截面把因子对 `[log(market_cap), one-hot(industry)]` 做 OLS,取残差,移除规模与行业暴露。缺行业 / 市值,或**残差自由度 ≤ 0**(名称数 ≤ 1+行业数,饱和拟合会给出无意义的伪 0 残差)时返回 **NaN**,绝不静默乱算;`processing.neutralize` 开启但协变量缺失(如 demo 路径)直接报可读错误。
- 实证:12 只票横跨 4 行业(2024-09-30),corr(原始 momentum, log市值) = -0.617 → 中性化后 -0.000,各行业残差均值 ≈ 0,确认规模/行业暴露被移除。
- **降级**:行业来自 `stock_basic.industry` 的**当前**行业标签,非按历史时点,故行业中性化带有轻微成分前视(市值 `daily_basic.total_mv` 为逐日真值)。PIT 行业历史是后续项,此降级在此显式披露(INV-007)。
78 changes: 78 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Quantitative_Trading — A股截面多因子框架

## 项目定位
- A股 **截面多因子选股**(cross-sectional multi-factor)框架。**不做择时**。
- 路径:研究/回测优先 → 渐进到实盘。
- 构建方式:成熟工具 + **自建因子层**为核心。
- 完整架构设计见 [`tmp/framework/architecture.html`](tmp/framework/architecture.html)。本文件是精简操作版,**细节以该文档为准**。

## 架构:7 层
```
data → universe → factors(特征) → alpha(合成/预测) → portfolio(+risk约束) → runtime(backtest|live) → analytics
```

| 层 | 职责 |
|---|---|
| `data/` | 采集(feed) / 清洗复权·对齐披露日(clean) / 面板存储(store) |
| `universe/` | PIT 成分股 + 可交易过滤(停牌/涨跌停/ST) |
| `factors/` | 截面因子 计算(compute) / 预处理三件套(process) / 落盘(store) |
| `alpha/` | 多因子合成/预测(独立成层) |
| `portfolio/` | 组合构建 + 风控**事前约束**(construct.py / risk.py) |
| `runtime/` | 回测 与 实盘 统一:driver + execution,两套实现(backtest/live) |
| `analytics/` | alphalens(因子检验) + quantstats(组合绩效) |

## 不可违反的设计不变量(写代码必须守)
1. **factors 永不碰未来收益**;只有 `alpha` 层才用未来收益拟合权重(防未来函数边界)。
2. **回测即实盘**:`runtime` 的 backtest/live 是同接口两实现;`factors/alpha/portfolio` 层两边复用、一行不改。
3. **分层解耦**:factor 不碰数据源;portfolio 不碰下单。坏味道:策略里直接 `pro.daily(...)`。
4. 决策是**截面的**(每个调仓日横向排序选股);时序只用于算因子。

## 致命陷阱(correctness 红线,详见 architecture.html §8)
未来函数 · PIT 成分股 · 可交易过滤 · 财务按 `ann_date` 披露日对齐 · 前复权 · batch≡incremental 一致性 · 行业+市值中性化 · 交易成本/换手 · 过拟合(样本内外+IC稳定性)。

## 环境
- **框架/测试环境**:conda `quant_mf`(Py 3.12)。运行 Phase 0、pytest、ruff、CLI 时用绝对路径 python:
`/home/shaofl/Development/env_tools/envs/quant_mf/bin/python`
- **数据拉取环境**:conda `data_fetch`(Py 3.12)。仅用于独立数据抓取/交互查数;在非交互 shell 里**用绝对路径 python**,不靠 activate:
`/home/shaofl/Development/env_tools/envs/data_fetch/bin/python`

## 数据:tushare
- **token**:`/home/shaofl/Projects/financial_projects/.config.json`(key `tushare.token`)。
⚠️ **绝不打印、绝不写进 repo、绝不 commit。** 代码里从该文件读取,不硬编码。
- **权限**:实测充足——个股日线 / 分钟(`stk_mins`) / 复权(`adj_factor`) / 成分股(`index_weight`) / 申万行业(`index_classify`) / 财务含`ann_date`(`income`,`fina_indicator`) 全可取。分钟级可直接上,无需先退回日线。
- **MCP(可选开发工具)**:`financial_projects/.mcp.json` 有 tushare MCP,仅供开发期交互查数;**从 `financial_projects/` 启动 claude 才加载**。
- **数据层 ETL 一律用 Python SDK(批量/增量),不要建在 MCP 上。** 注意 tushare 各接口有每分钟调用上限,批量拉取需限流+重试。

## 技术选型
| 用途 | 选型 |
|---|---|
| 数据处理 | pandas(+ polars 可选)· numpy |
| 存储 | parquet(分钟级按 symbol/year 分区)· DuckDB |
| 因子检验 | alphalens-reloaded |
| 绩效 | quantstats |
| 回归/合成 | statsmodels · scikit-learn |
| 组合优化(后期) | cvxpy · riskfolio-lib |
| 实盘下单(后期) | vnpy / miniqmt(QMT) —— **仅作下单通道,不当回测引擎** |
| 配置/测试 | pydantic-settings + YAML · pytest |

## 开发约定
- **交流中文**;代码/注释/commit message 用**英文**。
- **Git**:feature 分支 + PR。main 已有骨架;`data` 分支已含 P0+P1(PR #1 `data→main`,OPEN)。commit 用 conventional 格式,**无 attribution**(不加 Co-Authored-By)。
- **不过度设计**:按路线图 MVP 先打通一条端到端链路,再加层(architecture.html §11,Phase 0→3)。
- **secrets** 一律走外部 `.config.json`;repo `.gitignore` 已排除数据产物(`*.parquet`等)、缓存、`tmp/`(仅留架构文档)。
- 文件小而专(<800 行),immutable 优先。

## 当前进度
- ✅ 7 层骨架 + 架构文档(`main`)
- ✅ **Phase 0 MVP**(PR #1):DemoFeed → PanelStore → StaticUniverse → momentum_20 → zscore → EqualWeightAlpha → TopN 等权 → 月度回测(成本/换手)→ IC/绩效报告,单命令可复现。
- ✅ **Phase 1 偏差边界**(PR #1,全部真数据实证):
- 前复权(qfq;store 存 raw,内存复权 → batch≡incremental 安全)
- PIT 指数成分(`index_weight` as-of,survivorship-safe;370 天 pre-start 回看 + 90 天分页)
- 可交易过滤(停牌 / ST / 涨跌停;**涨跌停用未复权 raw close** 比 `stk_limit`)
- 财务 `ann_date` 披露日 as-of(绝不按 end_date;500 天 lookback carry forward)
- 行业 + 市值中性化(按 date 截面 OLS 残差;欠定/无自由度截面 → NaN)
- 路径感知降级披露(demo/static vs tushare/index/ann_date,绝不把 demo 当真实验证)
- ✅ 质量门:`pytest` **168 passed**;`ruff` clean;`validate-config`(demo + `config/example_tushare.yaml`)+ `run-phase0`(demo)均 OK。
- ✅ 真数据实证(tushare,非 CI):复权除权日 raw−5.74%→qfq+0.99% / CSI300 全年 24 快照 328 名换手 / ann_date Q1 延后至 04-20 / 中性化 corr −0.617→0。详见 `BIAS_AUDIT.md`、`artifacts/reports/phase1_summary.md`。
- ⚠️ 剩余 P2(已显式披露):行业标签用**当前值**非 PIT、涨跌停未方向感知、`min_listing_days` no-op、日线 only、简版 IC/绩效未走 alphalens/quantstats、demo 路径非真数据。
- 路线图下一步:财务因子组合 / 历史 PIT 行业 / 更细交易约束(architecture.html §11)。
Loading