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
10 changes: 7 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ data → universe → factors(特征) → alpha(合成/预测) → portfolio(+ri
- `analytics/alphalens_adapter.py`(IC mean/IR + 分位均值)+ `analytics/quantstats_adapter.py`(CAGR/Sharpe/maxDD/vol)薄 adapter,各带 `backend` 字段;`_import_*` 间接层使 import-missing 路径可测;alphalens stdout/warnings 已抑制。
- **简版仍权威**(驱动回测 + cross-check);依赖不可用/报错 → 报告披露 backend(unavailable/error,只记异常类型),**绝不静默假装**。
- **不改 alpha/portfolio/runtime/fills/universe**:P0/P2 交易数字不变(demo ic 0.96/annual 0.84 不变;alphalens IC=简版 IC 吻合),只新增 **Standard analytics** 报告段。
- ✅ 当前质量门:`pytest -p no:cacheprovider` **234 passed**;`ruff` clean;`validate-config`(demo + `example_tushare.yaml` + `phase2_real_baseline.yaml`)OK;`run-phase0`(demo)OK。
- ⚠️ 剩余 P2(已显式披露):日线 only、demo 路径非真数据。
- 路线图下一步:财务因子组合 / 分钟级(architecture.html §11)。
- 🔧 **Phase 3-1 首个真实多因子 baseline**(`p3-multifactor-financial-baseline` 分支,代劳待验收):pipeline 消费**全部 enabled factors**(曾只用第一个);`config/phase3_real_multifactor.yaml` = momentum_20 + roe + netprofit_yoy(SSE50 同窗口,与 phase2 可比;不调参、无 learned weights、非收益承诺)。
- 财务字段**一次 fetch + 一次 as-of 对齐**;合成仍 EqualWeightAlpha 等权;`drop_missing` 要求全因子齐备(披露);demo+财务因子仍可读报错;重名因子报错。
- 报告:active factor list / per-factor coverage+IC+分位 / combo score 诊断 / 财务 coverage 按字段(TRADED vs diagnostic);`output.baseline_report_name` 分离 phase3 报告。
- 真实结果:多因子 annual **−9.05%**(单因子 −10.19%);momentum_20 IC 0.0083(跨 run 一致)/ roe 0.0006 / netprofit_yoy 0.0001 / combo −0.0038;财务 ann_date 覆盖 100%;PIT SW-L1 98.53%。回归不破:phase2 rerun −10.19%/0.0083 不变,demo 0.96/0.84 不变。
- ✅ 当前质量门:`pytest -p no:cacheprovider` **249 passed**;`ruff` clean;`validate-config`(demo + `example_tushare.yaml` + `phase2_real_baseline.yaml` + `phase3_real_multifactor.yaml`)OK;`run-phase0`(demo)OK。
- ⚠️ 剩余(已显式披露):日线 only、demo 路径非真数据。
- 路线图下一步:alpha 加权合成(IC 加权/回归)/ 分钟级(architecture.html §11)。
1 change: 1 addition & 0 deletions BIAS_AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
- 拉取窗口向回看约 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 源会报可读错误,**不伪造财务**。
- **多因子(P3-1)**:多个财务字段(如 roe + netprofit_yoy)**一次 fetch、一次 as-of 对齐**(同一 `asof_financials` 调用,逐字段独立遵守 `ann_date <= trade_date`),无每因子重复拉取;财务字段可作为**被交易的因子**进入组合(不再只是诊断),报告按字段披露 TRADED vs diagnostic 角色与覆盖率。多因子合成是处理后(z-score/中性化)各列的**等权平均**(EqualWeightAlpha)——无 learned weights、不看 forward returns、不调参;`drop_missing` 要求该日该票**所有**启用因子齐备,缺任一因子即从该截面剔除(显式约定,绝不在部分数据上打分)。

## 复权

Expand Down
12 changes: 9 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ data → universe → factors(特征) → alpha(合成/预测) → portfolio(+ri
- `analytics/alphalens_adapter.py`(IC mean/IR + 分位均值)+ `analytics/quantstats_adapter.py`(CAGR/Sharpe/maxDD/vol)薄 adapter,各带 `backend` 字段。
- **简版 numpy/pandas 仍权威**(驱动回测 + cross-check);依赖不可用/报错 → 报告显式披露 backend(unavailable/error,只记异常**类型**不记消息),**绝不静默假装用了标准库**。
- **不改 alpha/portfolio/runtime/fills/universe**:P0/P2 交易数字不变(demo ic 0.96/annual 0.84 不变;实测 alphalens IC=简版 IC 完全吻合),只新增 **Standard analytics** 报告段。
- ✅ 质量门:`pytest` **234 passed**(P0=97 / P1=77 / P2-1=16 / P2-2=22 / P2-3=14 / P2-4=8);`ruff` clean;`validate-config`(demo + `example_tushare.yaml` + `phase2_real_baseline.yaml`)+ `run-phase0`(demo)均 OK。
- ⚠️ 剩余 P2(已显式披露):日线 only、demo 路径非真数据。
- 路线图下一步:财务因子组合 / 分钟级(architecture.html §11)。
- 🔧 **Phase 3-1 首个真实多因子 baseline**(`p3-multifactor-financial-baseline` 分支,代劳待验收):pipeline 从"只用第一个 enabled factor"升级为**消费全部 enabled factors**;新增 `config/phase3_real_multifactor.yaml`(momentum_20 + roe + netprofit_yoy,SSE50 同窗口,与 phase2 可比)。**不调参、无 learned weights、非收益承诺**。
- `_build_factors` 全量实例化(配置序;重名报错);财务字段**一次 fetch + 一次 as-of 对齐**(逐字段独立守 `ann_date <= trade_date`,无每因子重复拉取);demo+财务因子仍可读报错。
- 合成仍 `EqualWeightAlpha`(处理后各列等权平均,不看 forward returns);`drop_missing` 要求该日该票**所有**因子齐备(显式披露)。
- 报告增强:active factor list / per-factor coverage+IC+分位 / **combo score** 诊断 / 财务 coverage **按字段**披露(TRADED vs diagnostic 角色标注);`output.baseline_report_name` 使 phase3 报告独立于 phase2。
- **真实结果**(SSE50 2023-07~2024-06,~14min):多因子 annual **−9.05%**(单因子 −10.19%);per-factor IC: momentum_20 0.0083(与 phase2 run 完全一致,跨 run 一致性实证)/ roe 0.0006 / netprofit_yoy 0.0001;combo IC −0.0038;财务两字段 ann_date 覆盖 **100%**;PIT SW-L1 98.53%。财务因子在该小截面短窗口 IC≈0,照实披露——这是 plumbing 验证。
- **回归不破**:phase2 单因子真实 rerun annual −10.19% / IC 0.0083 不变;demo ic 0.96/annual 0.84 不变。
- ✅ 质量门:`pytest` **249 passed**(P0=97 / P1=78 / P2-1=20 / P2-2=22 / P2-3=14 / P2-4=8 / P3-1=10);`ruff` clean;`validate-config`(demo + `example_tushare.yaml` + `phase2_real_baseline.yaml` + `phase3_real_multifactor.yaml`)+ `run-phase0`(demo)均 OK。
- ⚠️ 剩余(已显式披露):日线 only、demo 路径非真数据。
- 路线图下一步:alpha 加权合成(IC 加权/回归)/ 分钟级(architecture.html §11)。
39 changes: 39 additions & 0 deletions RUNBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,45 @@ The phase2 baseline report (`artifacts/reports/phase2_real_baseline.md`) gains a
**Execution feasibility** section: per-rebalance blocked buys / blocked sells / carried
positions / executed turnover / invested fraction, from `BacktestDriver.feasibility_log()`.

## Phase 3-1 — first REAL multi-factor baseline (price + PIT financials)

P3-1 makes the pipeline consume **every enabled factor** (it previously used only
the first) and adds the first real multi-factor baseline: `momentum_20` + `roe` +
`netprofit_yoy`, combined by the SAME equal-weight alpha (row-wise mean of the
processed z-scored / neutralized columns). **No parameter search, no learned
weights, not a performance claim** — it validates the multi-factor plumbing.
Documented by `config/phase3_real_multifactor.yaml` (same SSE50 universe + window
as the phase2 baseline, so the two are comparable).

```bash
# validate (no network)
... -m qt.cli validate-config --config config/phase3_real_multifactor.yaml
# run the multi-factor real baseline (network + token; heavy, ~10-30 min)
... -m qt.cli run-phase2-baseline --config config/phase3_real_multifactor.yaml
```

The real-baseline RUNNER is shared with phase2 (`run-phase2-baseline` — same
diagnostics machine); `output.baseline_report_name` keeps the report separate:
`artifacts/reports/phase3_real_multifactor.md` (git-ignored, regenerable).

What P3-1 changes (locked by tests):

- **Multiple enabled factors** each become their own factor-panel column
(config order; duplicate names are a config error). Single-factor configs are
unchanged (primary factor = the only factor).
- **Financial fields are fetched ONCE** for all financial factors and as-of
aligned in a single `asof_financials` pass (`ann_date <= trade_date` per
field) — no per-factor refetch. Demo + financial factor still raises a
readable error (no fabricated financials).
- **Combination** is the equal-weight mean of the per-date processed columns
(`EqualWeightAlpha`); `drop_missing` requires ALL enabled factors for a name
on a date (disclosed — a name missing any factor is dropped from that
cross-section, never scored on partial data).
- **Report**: active factor list; per-factor coverage / IC / quantile
diagnostics plus the COMBO score's; financial ann_date coverage **per field**,
labelled TRADED factor vs diagnostic-only. Standard analytics stays
report-only (alphalens cross-checks the primary factor).

## Quality gate

```bash
Expand Down
35 changes: 28 additions & 7 deletions TEST_REPORT.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# TEST_REPORT — Phase 0 + Phase 1 + Phase 2 (bias-boundary → execution realism → PIT industry → standard analytics)
# TEST_REPORT — Phase 0 + Phase 1 + Phase 2 + Phase 3-1 (bias-boundary → execution realism → PIT industry → standard analytics → multi-factor)

## Commands

Expand All @@ -10,19 +10,20 @@ Run from the repo root with the project python (env `quant_mf`):
/home/shaofl/Development/env_tools/envs/quant_mf/bin/python -m qt.cli validate-config --config config/example.yaml
/home/shaofl/Development/env_tools/envs/quant_mf/bin/python -m qt.cli validate-config --config config/example_tushare.yaml
/home/shaofl/Development/env_tools/envs/quant_mf/bin/python -m qt.cli validate-config --config config/phase2_real_baseline.yaml
/home/shaofl/Development/env_tools/envs/quant_mf/bin/python -m qt.cli validate-config --config config/phase3_real_multifactor.yaml
/home/shaofl/Development/env_tools/envs/quant_mf/bin/python -m qt.cli run-phase0 --config config/example.yaml
```

## Results

| Gate | Command | Result |
|---|---|---|
| Unit + integration | `pytest -q` | **234 passed, 0 failed** |
| Unit + integration | `pytest -q` | **249 passed, 0 failed** |
| Lint | `ruff check .` | **All checks passed** |
| Config validation | `validate-config` (demo + `example_tushare.yaml` + `phase2_real_baseline.yaml`) | exit `0`, prints `OK` |
| Config validation | `validate-config` (demo + `example_tushare.yaml` + `phase2_real_baseline.yaml` + `phase3_real_multifactor.yaml`) | exit `0`, prints `OK` |
| End-to-end run | `run-phase0` (demo) | exit `0`, writes `artifacts/reports/phase0_summary.md` |

Counts below are the actual per-file `pytest` numbers (sum = 234).
Counts below are the actual per-file `pytest` numbers (sum = 249).

## Per-file breakdown — Phase 0 core (97)

Expand Down Expand Up @@ -58,7 +59,7 @@ Counts below are the actual per-file `pytest` numbers (sum = 234).
| `test_tradability_filters.py` | 7 | shared suspended/ST/limit filter |
| `test_tradability_enrich.py` | 6 | flag enrichment onto panel |
| `test_tushare_flags.py` | 3 | suspend_d/namechange/stk_limit feed |
| `test_pit_financials.py` | 7 | **ann_date as-of** (no disclosure leak) |
| `test_pit_financials.py` | 8 | **ann_date as-of** (no disclosure leak; +1 P3-1 multi-field single-pass) |
| `test_tushare_fina.py` | 2 | fina_indicator feed |
| `test_factors_financial.py` | 3 | financial factor (roe/netprofit_yoy) |
| `test_financial_pipeline.py` | 5 | factor dispatch + demo-source guard |
Expand All @@ -72,7 +73,7 @@ Counts below are the actual per-file `pytest` numbers (sum = 234).

| Test file | Tests | Feature |
|---|---|---|
| `test_phase2_baseline.py` | 16 | collectors, demo/real guard, report-field contract, no-secret-leak, settled-vs-candidate dates, loaded-vs-in-window membership, list_date + PIT-industry coverage, standard-analytics cross-check |
| `test_phase2_baseline.py` | 20 | collectors, demo/real guard, report-field contract, no-secret-leak, settled-vs-candidate dates, loaded-vs-in-window membership, list_date + PIT-industry coverage, standard-analytics cross-check, P3-1 factor-list/per-field-role/per-factor-table/report-name |

## Per-file breakdown — Phase 2-2 execution realism (22)

Expand All @@ -94,7 +95,13 @@ Counts below are the actual per-file `pytest` numbers (sum = 234).
|---|---|---|
| `test_quantstats_adapter.py` | 4 | quantstats perf metrics + unavailable / error fallback disclosure (no silent fake) |
| `test_alphalens_adapter.py` | 4 | alphalens IC / quantile metrics + unavailable / error fallback + stdout suppression |
| **Total (P0 + P1 + P2-1 + P2-2 + P2-3 + P2-4)** | **234** | |

## Per-file breakdown — Phase 3-1 multi-factor (10)

| Test file | Tests | Red-line / feature |
|---|---|---|
| `test_multifactor_pipeline.py` | 10 | all enabled factors built (order / disabled / duplicate / none), financials fetched ONCE for all fields + as-of both columns + input immutability, demo+financial readable error, e2e demo multi-factor panel + per-factor/combo analytics + primary==first, report factor list + combo + no secret, single-factor legacy shape |
| **Total (P0 + P1 + P2-1..P2-4 + P3-1)** | **249** | |

## Real-data validation (manual, not in CI — TEST-002 keeps the suite network-free)

Expand Down Expand Up @@ -142,6 +149,20 @@ Counts below are the actual per-file `pytest` numbers (sum = 234).
backends are disclosed (`backend` + exception TYPE only, no message) and keep the
simple fallback. Empirically the alphalens IC matched the simple IC exactly on the
demo (0.96), and the demo trading numbers (ic 0.96, annual 0.84) are unchanged.
- **P3-1 multi-factor (locked by tests):** the pipeline consumes EVERY enabled
factor (one factor-panel column each, config order; duplicate names are a
config error). Financial fields are fetched in ONE `fina_indicator` pass and
as-of aligned in ONE `asof_financials` call (each field independently honours
`ann_date <= trade_date`); demo + financial factor still raises a readable
error. The combination is the EQUAL-WEIGHT mean of the processed columns —
no learned weights, no forward-return fitting; `drop_missing` requires ALL
enabled factors (a name missing any factor is dropped from that
cross-section, disclosed). Single-factor configs keep their legacy shape and
numbers (demo ic 0.96 / annual 0.84 unchanged); the baseline report gains the
active factor list, per-factor coverage/IC/quantile tables, the combo-score
diagnostics, and per-field ann_date coverage labelled TRADED vs
diagnostic-only. `output.baseline_report_name` keeps the phase3 report file
separate from the phase2 one.
- A duplicate test-function name across two files was found and renamed during P2-2
(it had been silently shadowing one test in the full-suite run). A second, harmless
duplicate (`test_enrich_does_not_mutate_input` in two files) was verified NOT to drop
Expand Down
107 changes: 107 additions & 0 deletions config/phase3_real_multifactor.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Phase 3-1 — first REAL multi-factor baseline (price + PIT financial factors).
#
# Purpose: the FIRST multi-factor run on the real tushare path: momentum_20 (price)
# + roe + netprofit_yoy (ann_date PIT-aligned financials), combined by the same
# EQUAL-WEIGHT alpha (row-wise mean of the processed z-scored / neutralized
# columns). NO parameter search, NO learned weights, NOT a performance claim —
# it validates the multi-factor plumbing (batched financial fetch, per-factor
# diagnostics, combo score) against real data.
#
# Universe / window deliberately match config/phase2_real_baseline.yaml (SSE50,
# 2023-07 ~ 2024-06) so the single- vs multi-factor baselines are comparable.
#
# Run: python -m qt.cli run-phase2-baseline --config config/phase3_real_multifactor.yaml
# (the real-baseline runner is shared; output.baseline_report_name keeps this
# report separate from the phase2 one. Needs the tushare token in the external
# .config.json; hits the network; heavy.)

project:
name: quantitative_trading_phase3_real_multifactor
timezone: Asia/Shanghai

data:
source: tushare
freq: D
start: "2023-07-01"
end: "2024-06-30"
external_secret_file: "/home/shaofl/Projects/financial_projects/.config.json"
tushare_token_key: "tushare.token"
output_name: phase3_daily

universe:
type: index
index_code: "000016.SH"
symbols: []
min_listing_days: 60
filters:
missing_close: true
suspended: true
st: true
limit_up_down: true

factors:
- name: momentum_20
enabled: true
params:
window: 20
price_col: close
- name: roe
enabled: true
params: {}
- name: netprofit_yoy
enabled: true
params: {}

processing:
drop_missing: true
standardize:
enabled: true
method: zscore
winsorize:
enabled: false
method: mad
n: 3.0
neutralize:
enabled: true
industry_col: industry
size_col: market_cap
industry_level: L1 # PIT SW level (L1/L2/L3); L1 = 31 broad sectors (standard, DOF-safe)

alpha:
model: equal_weight
params: {}

portfolio:
constructor: topn_equal_weight
top_n: 20
long_only: true
max_weight: null
turnover_cap: null

backtest:
initial_nav: 1.0
rebalance: monthly
event_order: close_to_next_period
cash_return: 0.0

cost:
fee_rate: 0.001
slippage_rate: 0.0
turnover_formula: l1

analytics:
forward_return_periods:
- 1
- 5
- 20
quantiles: 5
benchmark: null

output:
root_dir: artifacts
data_dir: artifacts/data
factor_dir: artifacts/factors
report_dir: artifacts/reports
log_dir: artifacts/logs
overwrite: true
baseline_report_name: phase3_real_multifactor.md
4 changes: 4 additions & 0 deletions qt/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ class OutputCfg(_Strict):
report_dir: str = "artifacts/reports"
log_dir: str = "artifacts/logs"
overwrite: bool = True
# Filename for the real-baseline report (run-phase2-baseline). None keeps the
# historical default 'phase2_real_baseline.md'; a multi-factor baseline config
# sets its own name so it never overwrites the phase2 report (P3-1).
baseline_report_name: str | None = None


class RootConfig(_Strict):
Expand Down
Loading