---
title: "Portable Alpha"
description: "Factor decomposition, beta-neutral adjustments, and portable alpha strategies. Separate alpha from market exposure using OLS regression and EWMA tracking."
icon: "arrows-split-up-and-left"
---

<Warning>
  **Ultra Feature.** Requires an Ultra subscription. [Get started at api.mathematicalcompany.com](https://api.mathematicalcompany.com)
</Warning>

# 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

<CardGroup cols={2}>
  <Card title="Beta Decomposition" icon="chart-mixed">
    `hz.beta_decompose()` separates alpha from market exposure via OLS.
  </Card>
  <Card title="Factor Exposures" icon="layer-group">
    `hz.compute_factor_exposures()` computes betas across multiple factors.
  </Card>
  <Card title="Neutrality Adjustment" icon="scale-balanced">
    `hz.neutrality_adjustment()` calculates hedge ratios to reach target betas.
  </Card>
  <Card title="Incremental Beta" icon="gauge-high">
    `hz.incremental_beta_update()` tracks beta in O(1) per cycle via EWMA.
  </Card>
</CardGroup>

---

## Core Functions

### hz.beta_decompose

Decompose portfolio returns into alpha + beta * factor returns via OLS.

```python
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}")
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `portfolio_returns` | `list[float]` | Portfolio return series |
| `factor_returns` | `list[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.

```python
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.

```python
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.

```python
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}")
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `prev_cov` | `float` | Previous EWMA covariance |
| `prev_var` | `float` | Previous EWMA variance |
| `portfolio_return` | `float` | Latest portfolio return |
| `factor_return` | `float` | Latest factor return |
| `decay` | `float` | EWMA decay factor (e.g. 0.94) |

---

## Pipeline Functions

### hz.beta_decompose_pipeline

Decompose returns into alpha + beta each cycle.

```python
decomp = hz.beta_decompose_pipeline(
    portfolio_feed="my_portfolio",
    factor_feed="spy",
    window=50,
)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `portfolio_feed` | `str` | required | Feed name for portfolio |
| `factor_feed` | `str` | required | Feed name for factor/benchmark |
| `window` | `int` | `50` | Rolling window for OLS |

### hz.market_neutral

Track live beta and calculate hedge ratios for neutrality.

```python
neutral = hz.market_neutral(
    portfolio_feed="my_portfolio",
    factor_feed="spy",
    target_beta=0.0,
    tolerance=0.05,
    decay=0.94,
)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `portfolio_feed` | `str` | required | Portfolio feed |
| `factor_feed` | `str` | required | Factor feed |
| `target_beta` | `float` | `0.0` | Target beta exposure |
| `tolerance` | `float` | `0.05` | Acceptable deviation from target |
| `decay` | `float` | `0.94` | EWMA decay factor |

Returns each cycle:

```python
{
    "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.

```python
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).

```python
fm = hz.factor_model(
    factor_feeds={"market": "spy", "rates": "tlt"},
    decay=0.94,
)
```

---

## Examples

### Market-Neutral Strategy

```python
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

<AccordionGroup>
  <Accordion title="OLS Beta Decomposition">
    For portfolio returns R_p and factor returns R_f:

    **R_p = alpha + beta * R_f + epsilon**

    Beta = 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.
  </Accordion>
  <Accordion title="EWMA Incremental Beta">
    Instead of storing full history, use exponentially weighted moving averages:

    `cov_t = decay * cov_{t-1} + (1 - decay) * R_p * R_f`

    `var_t = decay * var_{t-1} + (1 - decay) * R_f^2`

    `beta_t = cov_t / var_t`

    With decay = 0.94 (approx 15-day half-life), recent observations matter more. This runs in O(1) per update.
  </Accordion>
  <Accordion title="Neutrality Adjustment">
    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.
  </Accordion>
</AccordionGroup>
