Add 5-day reversal end-to-end pipeline report and repro scripts
Runs the 5-day reversal signal through data→alpha→combo→portfolio on the full A-share universe and documents the finding: the naive z-score book loses to outlier concentration, rank weighting on a liquid universe recovers a real edge, and turnover-driven cost is the binding constraint. Includes the e2e driver and figure generator that produce the report. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
# 5-Day Reversal — End-to-End Pipeline Report
|
||||
|
||||
Generated: 2026-06-11T17:17:34
|
||||
|
||||
This report runs the **5-day reversal** signal end to end through the decoupled
|
||||
pipeline (`data → alpha → combo → portfolio build → portfolio simulate/eval`) on
|
||||
the full downloaded A-share universe, and answers the seven review questions:
|
||||
alpha storage, metric sanity, NaN/look-ahead handling, alpha↔portfolio
|
||||
closeness, alpha↔PnL closeness, per-phase timing, and visualizations.
|
||||
|
||||
Per this repo's convention an **alpha is a signed cross-sectional position
|
||||
weight, not a return predictor**, so evaluation is return / Sharpe / turnover /
|
||||
drawdown — there is deliberately **no IC/IR** anywhere.
|
||||
|
||||
## TL;DR
|
||||
|
||||
The naive built-in `reversal` alpha (raw `-pct_change(5)` then cross-sectional
|
||||
**z-score**) loses **-87.45%** in costless research on the full
|
||||
~5,200-name universe. That is **not** evidence the signal is bad — it is an
|
||||
artifact of z-score weighting on A-shares: a handful of newly listed /
|
||||
post-suspension / limit-up names produce huge `pct_change` outliers, and
|
||||
z-scoring pours the book into exactly those names (stored weights reach
|
||||
-52σ).
|
||||
|
||||
Switching only the **weighting** to a bounded cross-sectional **rank**
|
||||
(`reversal_rank`) and restricting to a per-date **liquid, non-ST, tradable**
|
||||
universe recovers a genuine reversal edge: **72.24%**
|
||||
costless research cumulative return at Sharpe
|
||||
**0.73** with a
|
||||
54.31% daily hit rate.
|
||||
|
||||
The binding constraint is **cost, not signal**: at ~148×/year
|
||||
turnover, a 10 bps round-trip (5 bps commission + 5 bps slippage) erases the
|
||||
edge — every variant is negative after costs. A tradable 5-day reversal needs
|
||||
turnover control, not a different signal.
|
||||
|
||||
## Headline Metrics
|
||||
|
||||
| run | weighting | research cum | research Sharpe | research turn/yr | exec before cost | exec net | exec net Sharpe |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| naive z-score (full) | z-score | -87.45% | -2.4515 | 160× | 18.39% | -111.94% | -1.4508 |
|
||||
| rank (full) | rank | -3.48% | -0.0198 | 143× | 50.52% | -66.61% | -1.1839 |
|
||||
| rank (liquid subset) | rank | 72.24% | 0.7310 | 148× | 110.18% | -17.16% | -0.2226 |
|
||||
|
||||
*Research = costless, no-look-ahead weights · next-day return. Execution = next-open
|
||||
fills on the discretized integer book under suspension / price-limit / volume-cap
|
||||
constraints, 5 bps commission + 5 bps slippage.*
|
||||
|
||||

|
||||
|
||||
## 1. Are Alpha Values Properly Stored?
|
||||
|
||||
All alpha artifacts conform to `ALPHA_COLUMNS` (`symbol_id, date, alpha_name,
|
||||
weight`), carry no null / non-finite weights, no duplicate `(symbol_id, date)`
|
||||
keys, and have numerically-zero daily cross-sectional means (weights are
|
||||
demeaned per date).
|
||||
|
||||
| run | schema ok | null w | non-finite w | dup keys | max |daily mean| | weight range | combo identity Δ |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| naive z-score (full) | True | 0 | 0 | 0 | 3.32e-16 | [-52.2, 19.2] | 0.00e+00 |
|
||||
| rank (full) | True | 0 | 0 | 0 | 0.00e+00 | [-2603.0, 2603.0] | 0.00e+00 |
|
||||
| rank (liquid subset) | True | 0 | 0 | 0 | 0.00e+00 | [-498.5, 498.5] | 0.00e+00 |
|
||||
|
||||
The decisive storage signal is the **weight range**. The naive z-score alpha
|
||||
stores weights as extreme as
|
||||
`[-52, 19]` —
|
||||
single names tens of sigma from the cross-section. Rank weighting is bounded by
|
||||
construction, so its stored weights are well-behaved. Same signal, completely
|
||||
different book.
|
||||
|
||||

|
||||
|
||||
## 2. Do The Alpha Metrics Make Sense?
|
||||
|
||||
Yes, and they tell a coherent story:
|
||||
|
||||
- The **z-score full** run is dominated by a few outlier names; its research
|
||||
Sharpe of -2.45 reflects a
|
||||
book that is effectively long/short a tiny set of extreme movers, which in
|
||||
A-shares keep trending — so the reversal bet loses.
|
||||
- **Rank full** (-3.48%) is roughly flat:
|
||||
the direction is right (hit rate
|
||||
51.18%) but
|
||||
the long tail of illiquid / ST / freshly listed names adds noise.
|
||||
- **Rank liquid** is the clean result: a positive, monotone reversal premium
|
||||
(72.24%, Sharpe
|
||||
0.73) once the
|
||||
investable universe is sane.
|
||||
|
||||
This matches the prior literature that short-horizon reversal is a real but
|
||||
liquidity- and cost-sensitive A-share effect.
|
||||
|
||||
## 3. NaN And Look-Ahead Handling
|
||||
|
||||
- The raw signal uses `close.pct_change(5, fill_method=None)` — missing prices
|
||||
are **not** forward-filled, so a suspended name does not silently inherit a
|
||||
stale price.
|
||||
- Weights are formed at close `t` and earn the **next** close-to-close return
|
||||
`t → t+1`. Forward returns are computed on the full market calendar *before*
|
||||
selecting signal dates, so a sparse signal grid still earns the next
|
||||
*available* return rather than the next signal date. The final signal date,
|
||||
which has no forward return, is dropped from metrics (that is why the
|
||||
research day count is one less than the stored signal-date count).
|
||||
- The liquid-universe mask is applied to the **signal**, not to the price
|
||||
history: `pct_change(5)` is always computed on contiguous prices, and the mask
|
||||
only decides what is *held*. It uses `tradestatus`, `isST`, a ≥60-session
|
||||
seasoning rule, and a trailing-20-day liquidity rank — all backward-looking.
|
||||
|
||||
## 4. How Close Are Alpha And Constructed Portfolio?
|
||||
|
||||
`portfolio build` normalizes the alpha to `target_weight = w / Σ|w|` and scales
|
||||
by booksize. The continuous target portfolio is an exact normalization of the
|
||||
stored alpha (research return correlation ≈ 1.0); the **integer** book then
|
||||
diverges because small per-name targets are rounded away under A-share lot
|
||||
rules.
|
||||
|
||||
| run | target_value identity max|Δ| | alpha→target max|Δ| | research corr(alpha,portfolio) | mean integer gross | mean L1 tracking |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| naive z-score (full) | 0.0000 | 0.00e+00 | 1.000000 | 9,138,331 | 2,542,655 |
|
||||
| rank (full) | 0.0000 | 0.00e+00 | 1.000000 | 8,984,098 | 2,678,278 |
|
||||
| rank (liquid subset) | 0.0000 | 0.00e+00 | 1.000000 | 9,810,256 | 862,303 |
|
||||
|
||||

|
||||
|
||||
## 5. How Close Are Alpha Metrics And Final PnL?
|
||||
|
||||
The costless research metric and the simulated net PnL diverge for two
|
||||
mechanical reasons, both quantified below: (a) **execution friction** — next-open
|
||||
fills, integer shares, and constraints; and (b) **cost** — the dominant term
|
||||
here.
|
||||
|
||||
| run | corr(alpha, exec net) | PnL before cost | total cost | net PnL | mean daily turnover |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| naive z-score (full) | 0.9675 | 1,838,974 | 13,032,720 | -11,193,746 | 0.5711 |
|
||||
| rank (full) | 0.9613 | 5,052,067 | 11,713,451 | -6,661,383 | 0.5133 |
|
||||
| rank (liquid subset) | 0.9762 | 11,017,842 | 12,733,803 | -1,715,960 | 0.5715 |
|
||||
|
||||
The research↔execution-net daily-return correlation stays high (the book *does*
|
||||
track the signal), but the level collapses after cost. For the liquid run, gross
|
||||
costless edge is real yet total cost
|
||||
(**12,733,803**)
|
||||
swamps it. This is the central finding: 5-day reversal is a signal you must trade
|
||||
*slowly* to monetize.
|
||||
|
||||

|
||||
|
||||
## 6. Time Consumption By Phase
|
||||
|
||||
| phase | rank full (s) | rank liquid (s) |
|
||||
| --- | --- | --- |
|
||||
| alpha compute | 108.3 | 116.1 |
|
||||
| alpha eval | 98.0 | 118.7 |
|
||||
| combo combine | 22.9 | 22.5 |
|
||||
| portfolio build | 599.8 | 254.3 |
|
||||
| portfolio eval | 94.2 | 90.0 |
|
||||
| portfolio simulate | 168.7 | 163.3 |
|
||||
| total | 1091.8 | 764.9 |
|
||||
|
||||

|
||||
|
||||
`portfolio build` dominates because it iterates per signal date and repairs a
|
||||
multi-thousand-name integer book under lot rules. The liquid run is faster
|
||||
across the board because it carries far fewer non-zero names per date.
|
||||
|
||||
## 7. Reproduce The Run
|
||||
|
||||
```bash
|
||||
# naive z-score baseline (full universe) — the built-in alpha, unchanged
|
||||
uv run python cli.py alpha compute --data-path data/daily_bars/all \
|
||||
--alpha-name reversal_5d_all --alpha-type reversal --lookback 5 --output-dir alphas
|
||||
|
||||
# robust rank weighting, full + liquid universe (one script, both runs)
|
||||
bash scripts/run_reversal_rank_e2e.sh
|
||||
|
||||
# regenerate this report + figures
|
||||
uv run python scripts/generate_reversal_5d_report.py
|
||||
```
|
||||
|
||||
## Interpretation & Next Steps
|
||||
|
||||
The pipeline is internally consistent end to end: storage validates, the trivial
|
||||
one-alpha combo is an exact identity, the continuous target portfolio matches the
|
||||
alpha, and the execution layer cleanly explains the gap to net PnL via friction
|
||||
and cost. The premise that 5-day reversal "produces not-bad PnL" holds **at the
|
||||
signal level** once weighting and universe are sane (rank + liquid), but **fails
|
||||
net of cost** at daily rebalance frequency.
|
||||
|
||||
Recommended next diagnostics:
|
||||
|
||||
- **Turnover control** — the highest-leverage lever: hold bands / no-trade zones,
|
||||
weight smoothing, or longer rebalance spacing to cut the ~150×/yr turnover.
|
||||
- Volatility-scaled or decayed reversal to reduce churn.
|
||||
- Sweep the liquidity cutoff and lookback to map the cost/edge frontier.
|
||||
Reference in New Issue
Block a user