500 nulls per rule. shift keeps the exact run structure (same exposure, same trades); perm keeps only the number of long bars. Low p on gross/calmar = real timing/risk skill; low p on raw net would be meaningless (drift).
| Sym | Rule | net p(f/v) shift | gross p(f/v) shift | cal p(f/v) shift | gross p(v) perm |
|---|---|---|---|---|---|
| BTC/USD | dual(200,60) | 0.166/0.092 | 0.166/0.086 | 0.060/0.138 | 0.046 |
| BTC/USD | donchian(55,20) | 0.310/0.062 | 0.310/0.062 | 0.174/0.182 | 0.072 |
| BTC/USD | donchian(100,50) | 0.430/0.138 | 0.430/0.138 | 0.230/0.000 | 0.080 |
| BTC/USD | roc(120) | 0.376/0.208 | 0.376/0.210 | 0.108/0.090 | 0.104 |
| ETH/USD | dual(200,60) | 0.226/0.084 | 0.226/0.086 | 0.030/0.102 | 0.060 |
| ETH/USD | donchian(55,20) | 0.538/0.110 | 0.538/0.110 | 0.468/0.026 | 0.050 |
| ETH/USD | donchian(100,50) | 0.594/0.406 | 0.594/0.404 | 0.320/0.088 | 0.492 |
| ETH/USD | roc(120) | 0.490/0.286 | 0.490/0.282 | 0.360/0.326 | 0.230 |
Read: if gross/calmar p stays high under shift, the return is drift the rule would have earned at any phase — no timing skill. If perm gross p is low, the rule's placement of the long bars beats random placement (exposure timing).