Files
2026-07-13 13:26:28 +08:00

208 lines
8.9 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
strategy_id: us_equities_panel
setup_version: v1
decision:
cadence: daily_close
snapshot: close
execution_delay: next_bar_open
universe:
n_assets: 3199
# Engine-level execution defaults. Single source of truth — Ch16-19 notebooks
# read these via get_backtest_config(); never declare a local INITIAL_CASH
# or share_type constant. Changing values here invalidates every existing
# backtest_hash for this case study.
#
# Spec-hash inputs (each invalidates every backtest_hash on change):
# - execution.initial_cash
# - execution.share_type
# - execution.allocator_lookback (CS-level fallback for moment allocators)
# - per-allocator overrides in backtest.sweep.allocators (e.g. lookback,
# vol_window, max_weight)
#
# ``allocator_lookback`` is the bars-of-underlying-price window applied
# uniformly to every moment-based allocator. Daily US equity bars → 63
# ≈ 3 months. ``mvo_ledoit_wolf`` carries an explicit per-allocator
# override below (126 bars ≈ 6 months) so N/K shrinkage degeneracy doesn't
# collapse it to identity-target at top_k=50.
#
# ``initial_cash`` restored to 1_000_000 (2026-05-16) — the 2026-05-15 SSOT
# migration drop to 100k caused catastrophic degenerate output here (top_k=50
# leg suspect; integer-share rounding × high-priced names). See
# memory/feedback_2026_05_15_equity_sizing_invalidated.md.
execution:
initial_cash: 1_000_000 # restores prior validated state
share_type: integer # US equities trade in whole shares
allocator_lookback: 63 # 3 months of daily bars (IV/RP/HRP fallback)
mapping:
class: long_short_decile_rebalance
position_state_space: long_short
entry_logic: decile_sort_long_top_short_bottom
sizing: equal_weight_within_decile
costs:
class: material
model: percentage # Loader path: bps regime via per_leg_cost_bps_range midpoint.
components: [spread, commission, market_impact, borrow_cost]
per_leg_cost_bps_range: [5, 20]
# per_share is the commission rate for the exploratory per-share
# cost-sensitivity regime (read by Ch18 18_costs.py and the run_sweep
# planner). IBKR Pro Tiered top tier. NOT used in the headline bps
# regime — bps regime is the production cost model.
per_share: 0.0035
# Documentation-only: loader reads top-level per_leg_cost_bps_range. The
# era split is qualitative — see feasibility §B.4 for why flat bps suffices
# given the validation window starts 2000-01-12 (pre-decimal era is ~6.5%
# of the window).
era_dependent:
pre_decimalization:
period: before 2001-01-29
per_leg_cost_bps_range: [15, 30]
note: Tick size 1/16 ($0.0625); wider spreads, higher commissions.
post_decimalization:
period: after 2001-01-29
per_leg_cost_bps_range: [5, 15]
note: Penny tick regime; electronic trading, lower spreads.
borrow_cost_note: Long-short requires borrow for the short leg (~50 bps/yr).
backtest:
rebalance:
# A rebalance is skipped when the per-asset weight change is below
# min_weight_change AND the resulting trade notional is below
# min_trade_value. The benchmark profile disables thresholds so that
# full-universe equal-weight (1/N per asset) rebalances at all.
default:
min_weight_change: 0.005
min_trade_value: 100.0
benchmark:
min_weight_change: 0.0
min_trade_value: 0.0
sweep:
# Iteration controls per stage. ``signal: 0`` means "all predictions";
# downstream stages take the top-N from the upstream stage's rank-1.
# Notebooks read these via get_top_n_predictions(case_study, stage).
top_n_predictions:
signal: 0 # all signal predictions (eq-weight baseline)
allocation_cheap: 0 # score_weighted, inverse_vol — all signal preds
allocation_expensive: 10 # risk_parity, mvo_ledoit_wolf, hrp — top-10 by signal Sharpe
cost_sensitivity: 1 # top-1 of {signal+allocation} per label
risk_overlay: 1 # top-1 of {signal+allocation} per label
# Skip MVO/HRP when allocator runtime is the bottleneck (intraday CSes).
expensive_allocators_skip: false
# Allocators routed through ``allocation_expensive`` (top-10 by signal Sharpe).
# All others (equal_weight, score_weighted, inverse_vol) take the cheap path.
expensive_allocators: [risk_parity, mvo_ledoit_wolf, hrp, conformal_weighted]
# Ch16 signal-stage selection. Long-short equal-weight top-k on three
# label horizons (1d primary, 5d/21d variants). The wider top-k grid
# reflects the larger universe (~3000 names vs ETFs' 100).
top_k_grid:
fwd_ret_1d: [20, 50]
fwd_ret_5d: [20, 50]
fwd_ret_21d: [20, 50]
# Ch17 portfolio: reuses top_k_grid above and sweeps over allocators.
# Moment-based allocators (IV/RP/HRP) use the CS-level
# ``execution.allocator_lookback`` (63 bars). ``mvo_ledoit_wolf`` gets
# an explicit 126-bar override (6 months) so N/K ≥ 2.5 at top_k=50 and
# Ledoit-Wolf shrinkage doesn't collapse to identity-target.
# SKIP_EXPENSIVE_ALLOC filters mvo_ledoit_wolf and hrp at notebook
# level when requested.
# No max_weight cap — see memory/feedback_max_weight_caps_intentionally_absent.md
# (capping near top_k forces equal-weight and defeats the comparison).
allocators:
- {name: equal_weight, method: equal_weight}
- {name: score_weighted, method: score_weighted}
- {name: inverse_vol, method: inverse_vol}
- {name: risk_parity, method: risk_parity}
- {name: mvo_ledoit_wolf, method: mvo_ledoit_wolf, lookback: 126}
- {name: hrp, method: hrp}
- {name: conformal_weighted, method: conformal_weighted}
# Ch18 cost sensitivity (bps regime — headline). A companion per-share
# regime is run from Ch18 cost notebooks for regime comparison; for
# us_equities_panel it is exploratory only because the wide price
# distribution + adjusted-price confounder make a flat-default
# half-spread an artifact rather than realistic friction.
cost_grid_bps: [0, 1, 2, 3, 5, 7, 10, 15, 20, 30, 50]
# Companion per-share half-spread grid (USD per share). Swept alongside
# cost_grid_bps by the planner. Values: 0¢, 0.5¢, 1¢, 2.5¢, 5¢, 10¢.
cost_grid_half_spread_usd: [0.0, 0.005, 0.01, 0.025, 0.05, 0.10]
# Ch19 risk overlays.
risk_controls:
position:
- {name: stop_loss_3pct, type: stop_loss, threshold: 0.03}
- {name: stop_loss_5pct, type: stop_loss, threshold: 0.05}
- {name: stop_loss_10pct, type: stop_loss, threshold: 0.10}
- {name: stop_loss_15pct, type: stop_loss, threshold: 0.15}
- {name: trailing_1pct, type: trailing_stop, threshold: 0.01}
- {name: trailing_2pct, type: trailing_stop, threshold: 0.02}
- {name: trailing_3pct, type: trailing_stop, threshold: 0.03}
- {name: trailing_5pct, type: trailing_stop, threshold: 0.05}
- {name: trailing_10pct, type: trailing_stop, threshold: 0.10}
- {name: trailing_15pct, type: trailing_stop, threshold: 0.15}
- {name: trailing_20pct, type: trailing_stop, threshold: 0.20}
- {name: time_exit_10, type: time_exit, bars: 10}
- {name: time_exit_20, type: time_exit, bars: 20}
- {name: time_exit_40, type: time_exit, bars: 40}
evaluation:
n_splits: 16
train_size: 10Y
val_size: 1Y
holdout_start: '2016-01-01'
holdout_end: '2018-03-31'
calendar: NYSE
periods_per_year: 252 # NYSE 5d/wk
labels:
primary: fwd_ret_1d
buffer: 1D
variants:
- fwd_ret_5d
- fwd_ret_21d
variant_buffers:
fwd_ret_5d: 5D
fwd_ret_21d: 21D
# Vectorized-backtest thinning step per label: number of schedule slots
# to advance per trade so holding periods don't overlap.
rebalance_step:
fwd_ret_1d: 1
fwd_ret_5d: 5
fwd_ret_21d: 21
modeling:
gbm:
libraries: [lightgbm]
preset: default
device: cpu
latent_factors:
persistent_entities: true
# Down-tune the SDF schedule on this large-N CS so training time stays
# in the overnight budget. Paper defaults (256/64/1024) on broad US
# equities at daily frequency would take many hours per fold.
model_kwargs:
sdf:
n_epochs_unc: 128
n_epochs_moment: 32
n_epochs_cond: 512
burn_in_epochs: 32
checkpoint_epochs: [128, 256, 384, 512, 640]
beta_checkpoint_epochs: [256]
beta_default_checkpoint: 256
causal:
treatment: past_ret_12m_skip
confounders: [vol_21d, amihud_illiq, volume_ratio]
method: walk_forward_dml
# Read by 20_strategy_analysis.py to gate the headline strategy summary.
kill_conditions:
ic_floor: 0.01
ic_floor_note: Cross-sectional IC below 0.01 across all features.
edge_to_cost_floor: 1.2
edge_to_cost_note: Net Sharpe / cost ratio below 1.2x.
micro_cap_concentration: 0.5
micro_cap_note: Alpha concentrated >50% in bottom ADV quintile (untradeable).
net_sharpe_floor: 0.3
net_sharpe_note: Net Sharpe after borrow costs below 0.3.