CarverSystematic

Robert Carver's vol-targeted framework with forecast scaling, FDM, and buffering

CarverSystematic implements the sizing framework from Robert Carver’s Systematic Trading and Leveraged Trading. It’s particularly well-suited to multi-instrument systematic strategies where you want explicit vol targeting and low turnover.

Import

python
from horizon.portfolio import CarverSystematic

The five moving parts

Raw forecast

Strategy emits a number. z-score, momentum, whatever.

Forecast scaling

Scale so that average |forecast| = 10 (by convention).

Forecast capping

Cap at ±20 to prevent outliers from dominating.

Forecast diversification multiplier (FDM)

Correlations between combined forecasts → multiplier that accounts for the benefit.

Vol-targeted position sizing

position_usd = (forecast / 10) × (target_vol × equity) / instrument_vol × FDM

Plus buffering: only rebalance if the desired change exceeds a buffer width.

Signature

python
CarverSystematic(
    annual_vol_target: float = 0.20,
    forecast_scalar_method: str = "historical",
    forecast_cap: float = 20.0,
    diversification_multiplier: float | str = 1.0,
    fdm_bootstrap_samples: int = 1000,
    buffering: bool = True,
    buffer_width: float = 0.10,
    vol_estimator: str = "ewma",
    vol_lookback: int = 90,
    vol_floor: float = 0.05,
)
annual_vol_targetfloat
Target annualized portfolio volatility. `0.20` = 20% annualized.
forecast_scalar_methodstr
How to compute the forecast scalar. `"historical"` = estimate from rolling mean of absolute forecasts. `"fixed"` = use 1.0 (no scaling).
forecast_capfloat
Symmetric cap applied after scaling.
diversification_multiplierfloat | str
Either a fixed FDM value or `"bootstrap"` to estimate from history.
bufferingbool
Enable position buffering to reduce turnover.
buffer_widthfloat
Rebalance only if `|target - current| > buffer_width × target`. `0.10` = 10% of target.
vol_estimatorstr
Volatility estimation method. `"ewma"`, `"garch"`, `"realized"`.
vol_lookbackint
Lookback window for the vol estimator.
vol_floorfloat
Minimum instrument vol to use in the division. Prevents divide-by-zero on low-vol instruments.

Emitting Carver-style forecasts

Strategies that target Carver sizing should emit signals with carver_forecast:

python
Signal(
    market_id=m.id,
    direction=Direction.Increase if forecast > 0 else Direction.Decrease,
    carver_forecast=15,                 # Carver scale, ±20
    confidence=0.8,
    expected_edge_bps=50,
    expected_expected_vol_bps=2000,
    horizon=timedelta(days=20),
)

Or use the helper:

python
Signal.from_carver_forecast(
    market=m,
    forecast=15,
    instrument_vol=0.20,
    horizon="20d",
)

CarverSystematic reads carver_forecast preferentially; if not set, it synthesizes one from the signal’s sharpe property.

Use it

python
import horizon as hz
from horizon.portfolio import CarverSystematic

result = hz.run(
    portfolio=CarverSystematic(
        annual_vol_target=0.15,
        buffering=True,
        buffer_width=0.10,
        forecast_cap=20,
    ),
    ...
)

Buffering

The most turnover-impacting feature. When buffering=True:

python
delta = target_position - current_position
if abs(delta) < buffer_width × abs(target_position):
    # Don't trade. delta is too small relative to target
    keep current position

For a target of $10,000 and buffer_width=0.10, the buffer is $1,000. The strategy won’t trade unless the desired change is at least $1,000.

Benefit: turnover drops dramatically without sacrificing much expected return. Carver’s books argue that tight buffers (10-20%) capture most of the benefit.

FDM. Forecast Diversification Multiplier

When you combine multiple (semi-)independent forecasts into one position, the combination has lower variance than the sum of individual variances. The FDM captures this benefit:

effective_vol = sum_individual_vols × √(1 / n_effective)
FDM          = 1 / √(avg_correlation_factor)

Typical values:

  • 1 forecast → FDM = 1.0
  • 2 uncorrelated forecasts → FDM = √2 ≈ 1.41
  • 4 uncorrelated forecasts → FDM = 2.0
  • 4 highly correlated forecasts → FDM ≈ 1.2

Higher FDM means “we’re more diversified, so we can size up without increasing realized vol.”

Set fixed:

python
CarverSystematic(diversification_multiplier=1.5)

Or estimate from bootstrap:

python
CarverSystematic(
    diversification_multiplier="bootstrap",
    fdm_bootstrap_samples=1000,
)

The bootstrap path samples historical forecasts, computes FDM for each sample, and takes the median for robustness.

When to use

Vol-targeted mandates If your mandate is "run at 15% annualized vol", CarverSystematic directly targets that.
Multi-instrument systematic strategies 10+ instruments with (semi-)independent signals. FDM captures the diversification.
Low-turnover portfolios Buffering cuts turnover by 60-80% on typical signals. Saves transaction costs.
When you don't have a reliable edge estimate Carver sizes by vol, not by expected return. Less sensitive to edge miscalibration than Kelly.

When NOT to use

Pitfalls

Reference

  • Robert Carver, Systematic Trading (2015)
  • Robert Carver, Leveraged Trading (2020)

Next