Horizon Design

SkillMedia

Choose prediction horizon by analyzing IC decay, turnover cost, and feature-horizon alignment. Use when determining the optimal lookahead window for labels.

Use Horizon Design in Claude, ChatGPT or Ahel Desktop

Free. Sign in, add Horizon Design and connect your AI. About a minute.

Also: Claude Code · Cursor · Codex

Then ask your AI: use the Horizon Design skill

Details

Instructions available. Your AI can read the instructions. Execution depends on the setup they require.

Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Horizon DesignStart free

What this skill tells your AI

The instructions your AI receives, as published by ml4t/skills in features/horizon-design/SKILL.md and read by Ahel’s review.

An arbitrary 1-day horizon forces daily rebalancing, which costs 2-5% annually in transaction costs. If the signal's IC peaks at 20 days, you are paying for turnover that destroys the edge.

The Problem

The prediction horizon determines everything downstream: label construction, feature relevance, turnover, and whether transaction costs leave any alpha. Choosing it arbitrarily - or defaulting to "1 day because that is what everyone uses" - misaligns the model with the actual signal dynamics.

The Pattern

WRONG

import numpy as np

# Arbitrary 1-day horizon - no evidence this matches the signal
labels = np.roll(returns, -1)  # forward 1-day return as label
# Result: high turnover, transaction costs eat the edge

CORRECT

from scipy.stats import spearmanr
import numpy as np

def find_optimal_horizon(
    signal: np.ndarray, returns: np.ndarray, horizons: list[int] = None,
) -> dict:
    """Analyze IC decay to find the horizon where the signal is strongest."""
    if horizons is None:
        horizons = [1, 2, 5, 10, 20, 40, 60]

    results = {}
    for h in horizons:
        fwd_ret = np.full_like(returns, np.nan)
        fwd_ret[:-h] = np.sum(
            [np.roll(returns, -i) for i in range(1, h + 1)], axis=0
        )[:-h]
        valid = ~np.isnan(signal) & ~np.isnan(fwd_ret)
        ic, _ = spearmanr(signal[valid], fwd_ret[valid])
        results[h] = ic

    optimal = max(results, key=lambda k: abs(results[k]))
    return {"ic_by_horizon": results, "optimal_horizon": optimal}

IC Decay Profile

HorizonTypical ICInterpretation
1d0.01Too noisy, costs dominate
5d0.03Building strength
20d0.05Peak - optimal horizon
40d0.03Decaying
60d0.01Signal exhausted

Transaction Cost Constraint

def min_viable_horizon(cost_per_trade: float, annual_alpha: float) -> int:
    """Shortest horizon where alpha covers costs."""
    for h in [1, 2, 5, 10, 20, 40, 60]:
        trades_per_year = 252 / h
        alpha_per_trade = annual_alpha / trades_per_year
        if alpha_per_trade > cost_per_trade * 2.5:  # 2.5x safety margin
            return h
    return 60  # Default to low-frequency if costs are high

Feature-Horizon Alignment

# Misaligned: 5-day feature predicting 60-day returns
feature = returns_5d
label = fwd_returns_60d

# Aligned: 60-day feature predicting 60-day returns
feature = returns_60d
label = fwd_returns_60d

Guardrails

  • Never default to 1-day without IC decay analysis - most alpha signals peak at 5-20 days
  • Transaction costs are the binding constraint - a 5-day signal with 10 bps costs beats a 1-day signal with the same IC
  • Feature lookback should match horizon within a factor of 2-3x

Production Implementation

from ml4t.diagnostic.metrics import compute_ic_by_horizon

ic_by_horizon = compute_ic_by_horizon(
    predictions=prediction_frame,
    prices=price_frame,
    horizons=[1, 5, 10, 20, 60],
    pred_col="prediction",
    price_col="close",
    date_col="date",
    group_col="symbol",
)
print(ic_by_horizon)

Checklist

  • IC decay analysis run across at least 5 horizons (1d, 5d, 10d, 20d, 60d)
  • Optimal horizon identified as the peak of |IC| vs horizon
  • Transaction costs modeled - alpha per trade exceeds cost by at least 2.5x
  • Feature lookback windows aligned with chosen horizon
  • Rebalancing frequency matches horizon (not more frequent)

Signals

GitHub stars
22
Forks
11
Last commit
Oct 2026
Advanced
Item type
skill
Key
ml4t-horizon-design
Source
github.com/ml4t/skills