Vectra
Operations6 min read · May 9, 2026

Iter 20: the dead-zone filter that lifted CAGR 0.78pp

We found a uniquely negative bucket in the GBDT signal distribution (1.80-2.00, 39.9% win rate). Filtering it lifted wf-CAGR +0.78pp and dropped stitched DD -2.49pp. Free wins like this are rare; documenting how we caught it.

Vectra ships under a strict acceptance gate. Most attempted improvements get rejected — the iter log has 13+ ruled-out variants on file. Iter 20's "dead-zone filter" is one of the rare ones that passed. It lifted CAGR by +0.78pp/yr while cutting stitched drawdown by 2.49pp. This post is how we found it.

The setup

Iter 19 looked at the per-bucket P&L distribution of the GBDT's signal-at-entry score. We sliced the 1,614 OOS trades into 7 buckets by signal magnitude and computed mean per-trade return in each.

signal bucket    n    mean ret%    win%    contrib
1.50–1.80       64    +0.74%      54.7%   +47%
1.80–2.00      143    -0.11%      39.9%   -16%   ←
2.00–2.30      237    +0.80%      51.5%  +190%
2.30–2.60      242    +0.31%      51.7%   +74%
2.60–3.00      231    +0.89%      58.4%  +205%
3.00–3.50      186    +1.02%      51.6%  +189%
3.50+           98    +1.93%      64.3%  +189%
text

The 1.80–2.00 bucket is the only negative one across all 7. It's also the only sub-50% win-rate bucket. 143 trades, −0.11% mean, 39.9% wr.

The hypothesis

Trades whose signal-at-entry falls in [1.80, 2.00] are system-atically losing money. If we filter them out at the gate, we should see a CAGR lift roughly equal to the bucket's negative contribution, with a comparable DD reduction from removing the variance.

Suspect this finding before validating it. A negative bucket in a single backtest could be window-pick luck. If we'd cherry-picked a bucket from a single year, that would be exactly the kind of overfitting the gates exist to catch.

Validating

We tested 7 different bound choices around the [1.80, 2.00] window — narrower, wider, shifted up, shifted down. The locked choice has to be uniquely optimal across these neighbours; otherwise it's a tunable knob and the multiple-comparison correction kills it.

bounds [LO, HI]   trades filtered   CAGR     DD       vs locked
[1.70, 1.95]      -134              +40.49%  18.19%   -4.37pp CAGR
[1.75, 2.00]      -123              +41.86%  17.86%   -3.00pp CAGR
[1.80, 2.00]      -101              +44.86%  17.86%   ← locked, best
[1.80, 2.05]      -120              +42.64%  19.59%   -2.22pp + DD over
[1.80, 2.10]      -150              +41.48%  18.67%   -3.38pp CAGR
[1.85, 2.05]      -90               +44.01%  20.32%   -0.85pp + DD over
[1.75, 2.05]      -144              +39.74%  19.59%   -5.12pp + neg fold
text

The [1.80, 2.00] window is uniquely optimal. Every adjacent choice underperforms by ≥0.85pp CAGR or breaks the gate-3 DD ceiling. That's what a knife-edge optimum looks like — and it's the kind of finding that survives multiple-comparison correction because it's not arbitrary.

The implementation

Two env vars + one branch in the entry gate. Total diff: 8 files touched, 191 lines of additions, 4 deletions. The change is in commit cb068eb.

// vectra-strategy-core/src/gates.rs::should_enter
if input.eff_sig.abs() < params.entry_threshold {
    return EntryDecision::Block(BlockReason::BelowThreshold);
}
// Iter 19/20 — dead-zone filter.
if params.dead_zone_high > params.dead_zone_low &&
   params.dead_zone_low > 0.0 {
    let abs_sig = input.eff_sig.abs();
    if abs_sig >= params.dead_zone_low &&
       abs_sig <= params.dead_zone_high {
        return EntryDecision::Block(BlockReason::DeadZone);
    }
}
rust

Both bounds default to 0.0 (filter disabled). Live config sets them to 1.80 and 2.00. Backtest engine plumbs them through the same CoreVolMomParams struct, so backtest and live agree.

Why this lifted DD too

Naive intuition: filtering 101 trades should reduce CAGR proportionally. It doesn't, because we're filtering trades whosecontribution was negative. So we're recovering 16 percentage points of negative contribution that compound through the equity curve.

The DD reduction (−2.49pp stitched) is a different mechanism. The 1.80–2.00 bucket had 60% loss rate. Each fold's drawdown is mostly composed of consecutive losses; removing the bucket thins out the loss density and shortens the drawdown depth.

What this taught us

Per-bucket P&L analysis is now part of the standard post-iteration checklist. Buckets sliced by signal strength, regime, time-of-day, and per-symbol contribution are all worth looking at after every backtest. Most reveal nothing actionable. This one happened to.

The full sweep is documented in docs/phase-overhaul-overnight-improvements.md iter 19 and iter 20 sections, including the negative findings from time-of-day filtering and per-symbol calibration that we ruled out at the same time.

Published by Floris V. · Vectra operator

May 9, 2026

Join the waitlist →