Skip to main content
Ultra Feature. Requires an Ultra subscription. Get started at api.mathematicalcompany.com

Portable Alpha

A strategy can appear profitable when it is just riding market beta. Horizon’s portable alpha module decomposes returns into alpha (skill) and beta (market exposure), tracks factor betas incrementally, and calculates the hedge ratios needed to neutralize unwanted exposures. All regression math is in Rust.

Overview

Beta Decomposition

hz.beta_decompose() separates alpha from market exposure via OLS.

Factor Exposures

hz.compute_factor_exposures() computes betas across multiple factors.

Neutrality Adjustment

hz.neutrality_adjustment() calculates hedge ratios to reach target betas.

Incremental Beta

hz.incremental_beta_update() tracks beta in O(1) per cycle via EWMA.

Core Functions

hz.beta_decompose

Decompose portfolio returns into alpha + beta * factor returns via OLS.
import horizon as hz

result = hz.beta_decompose(
    portfolio_returns=[0.01, -0.02, 0.015, 0.005, -0.01],
    factor_returns=[0.008, -0.015, 0.012, 0.003, -0.008],
)
print(f"Alpha: {result.alpha:.6f}")
print(f"Beta: {result.beta:.4f}")
print(f"R-squared: {result.r_squared:.4f}")
print(f"Tracking error: {result.tracking_error:.4f}")
print(f"Information ratio: {result.information_ratio:.4f}")
ParameterTypeDescription
portfolio_returnslist[float]Portfolio return series
factor_returnslist[float]Factor/benchmark return series
Returns an AlphaDecomposition with fields: alpha, beta, r_squared, tracking_error, information_ratio.

hz.compute_factor_exposures

Multi-factor regression: compute betas across multiple factors.
exposures = hz.compute_factor_exposures(
    portfolio_returns=port_ret,
    factor_names=["market", "size", "momentum"],
    factor_returns_matrix=[mkt_ret, smb_ret, mom_ret],
)
for e in exposures:
    print(f"{e.factor_name}: beta={e.beta:.4f}, R2={e.r_squared:.4f}")

hz.neutrality_adjustment

Calculate hedge ratios needed to reach target betas.
adjustments = hz.neutrality_adjustment(
    current_betas=[0.8, -0.3],
    factor_names=["market", "size"],
    target_betas=[0.0, 0.0],
    tolerances=[0.05, 0.05],
)
for a in adjustments:
    print(f"{a.factor_name}: hedge={a.hedge_ratio:+.4f}, ok={a.is_within_tolerance}")

hz.incremental_beta_update

O(1) EWMA-based beta tracking. No need to store full history.
cov, var, beta = hz.incremental_beta_update(
    prev_cov=0.0, prev_var=0.0,
    portfolio_return=0.01, factor_return=0.008,
    decay=0.94,
)
print(f"Live beta: {beta:.4f}")
ParameterTypeDescription
prev_covfloatPrevious EWMA covariance
prev_varfloatPrevious EWMA variance
portfolio_returnfloatLatest portfolio return
factor_returnfloatLatest factor return
decayfloatEWMA decay factor (e.g. 0.94)

Pipeline Functions

hz.beta_decompose_pipeline

Decompose returns into alpha + beta each cycle.
decomp = hz.beta_decompose_pipeline(
    portfolio_feed="my_portfolio",
    factor_feed="spy",
    window=50,
)
ParameterTypeDefaultDescription
portfolio_feedstrrequiredFeed name for portfolio
factor_feedstrrequiredFeed name for factor/benchmark
windowint50Rolling window for OLS

hz.market_neutral

Track live beta and calculate hedge ratios for neutrality.
neutral = hz.market_neutral(
    portfolio_feed="my_portfolio",
    factor_feed="spy",
    target_beta=0.0,
    tolerance=0.05,
    decay=0.94,
)
ParameterTypeDefaultDescription
portfolio_feedstrrequiredPortfolio feed
factor_feedstrrequiredFactor feed
target_betafloat0.0Target beta exposure
tolerancefloat0.05Acceptable deviation from target
decayfloat0.94EWMA decay factor
Returns each cycle:
{
    "beta": 0.45,
    "hedge_ratio": -0.45,
    "neutral": False,
    "target_beta": 0.0,
}

hz.portable_alpha

Full multi-factor portable alpha with exposure tracking and adjustment signals.
pa = hz.portable_alpha(
    portfolio_feed="my_portfolio",
    factor_feeds={"market": "spy_feed", "rates": "tlt_feed"},
    target_betas={"market": 0.0, "rates": 0.0},
    tolerance=0.05,
)

hz.factor_model

Track factor betas incrementally (O(1) per cycle).
fm = hz.factor_model(
    factor_feeds={"market": "spy", "rates": "tlt"},
    decay=0.94,
)

Examples

Market-Neutral Strategy

import horizon as hz

def alpha_strategy(ctx):
    if not ctx.params.get("neutral", False):
        return []  # not neutral yet, wait
    return hz.quotes(fair=ctx.feed.price, spread=0.03, size=10)

hz.run(
    name="market_neutral",
    exchange=[hz.Polymarket(), hz.Alpaca(paper=True)],
    feeds={
        "market": hz.PolymarketBook("will-btc-hit-100k"),
        "spy": hz.AlpacaFeed(symbols=["SPY"]),
    },
    pipeline=[
        hz.market_neutral(
            portfolio_feed="market",
            factor_feed="spy",
            target_beta=0.0,
            tolerance=0.05,
        ),
        alpha_strategy,
    ],
)

Mathematical Background

For portfolio returns R_p and factor returns R_f:R_p = alpha + beta * R_f + epsilonBeta = Cov(R_p, R_f) / Var(R_f). Alpha is the intercept (excess return after removing factor exposure). R-squared measures the fraction of variance explained by the factor.
Instead of storing full history, use exponentially weighted moving averages:cov_t = decay * cov_{t-1} + (1 - decay) * R_p * R_fvar_t = decay * var_{t-1} + (1 - decay) * R_f^2beta_t = cov_t / var_tWith decay = 0.94 (approx 15-day half-life), recent observations matter more. This runs in O(1) per update.
To reach target beta from current beta:hedge_ratio = -(current_beta - target_beta)A positive hedge_ratio means add long factor exposure; negative means short it.