# ETF Trend System (K<=4, no leverage) - v2 This is a daily-signal / daily-rebalance trend-following system on a configurable ETF universe. It is designed for: - K<=4 holdings - no leverage (net exposure <= 100%) - portfolio vol cap (de-risk only), remainder parked in a rates ETF - practical execution hygiene (cooldown / new listing protection / turnover band) ## Signals - Trend filter (entry universe): MA(fast) > MA(slow) - default: MA5 > MA20 - Ranking score (higher is better): `score = (0.5*R20 + 0.3*R60 + 0.2*R120) / max(vol20, floor) + 0.5*trend_strength` where `trend_strength = ma_fast/ma_slow - 1`. ## Entry On each rebalance day (daily): - Candidate must satisfy: - `trend_ok == True` (MA cross) - `score >= min_score` - `min_history_days` protection (skip too-new series) - `cooldown_days` protection (after exit, avoid immediate re-entry) ## Position Sizing - Risk parity on `vol20` across selected holdings. - Per-asset cap: `max_weight_per_asset` (default 0.50) - Portfolio vol cap (no leverage): `scale = min(1, target_ann_vol / port_vol(port_vol_window))` Remaining weight (1 - sum(weights)) is parked in `rates_fallback`. ## Exits (checked daily) A position exits if any triggers: - Trend break: MA(fast) < MA(slow) - Chandelier stop: close < highest_close - atr_mult*ATR - Stop loss from entry: close < entry_price - stop_loss_atr*ATR - Take profit from entry: close > entry_price + take_profit_atr*ATR ## Trading Hygiene - `rebalance_band`: ignore small weight changes to reduce churn. - `min_hold_days`: do not rebalance-sell a very fresh position (risk exits still apply). - `new_asset_days/new_asset_max_w`: cap weight of a newly-eligible asset for its first N tradable days after it passes the history gate. ## Outputs Backtest runner writes 3 artifacts: - equity curve parquet: `data/etf_trend_equity_*.parquet` - weights parquet: `data/etf_trend_equity_*_weights.parquet` - trades parquet: `data/etf_trend_equity_*_trades.parquet`