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
from horizon.portfolio import CarverSystematic
The five moving parts
Raw forecast
Forecast scaling
average |forecast| = 10 (by convention).Forecast capping
Forecast diversification multiplier (FDM)
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
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_targetfloatforecast_scalar_methodstrforecast_capfloatdiversification_multiplierfloat | strbufferingboolbuffer_widthfloatvol_estimatorstrvol_lookbackintvol_floorfloatEmitting Carver-style forecasts
Strategies that target Carver sizing should emit signals with carver_forecast:
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:
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
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:
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:
CarverSystematic(diversification_multiplier=1.5)
Or estimate from bootstrap:
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
When NOT to use
Pitfalls
Reference
- Robert Carver, Systematic Trading (2015)
- Robert Carver, Leveraged Trading (2020)