Skip to main content
Pro Feature. Requires a Pro or Ultra subscription. Get started at api.mathematicalcompany.com
What is this? When you have many potential trading signals, elastic net regression automatically selects the ones that actually predict returns and discards the rest. It’s a regularized regression that balances between LASSO (which selects few signals) and ridge (which shrinks all signals). Use it to build parsimonious alpha models that don’t overfit.

Elastic Net Selection

Horizon provides Rust-native regularized regression (elastic net, lasso, ridge) for feature selection and signal construction. These methods identify which market signals carry predictive power and automatically shrink or eliminate noisy features, producing sparse, interpretable models suitable for real-time trading.

Elastic Net

Combined L1 + L2 regularization. Balances feature selection (lasso) with coefficient stability (ridge).

Lasso (L1)

Pure L1 regularization. Drives uninformative coefficients to exactly zero for automatic feature selection.

Ridge (L2)

Pure L2 regularization. Shrinks all coefficients toward zero without eliminating any. Handles multicollinearity.

Cross-Validation

Automated alpha/lambda selection via k-fold CV. Finds the regularization strength that minimizes out-of-sample error.

hz.elastic_net_fit

Fit an elastic net regression model with combined L1 and L2 penalties. The objective minimizes: (1/2n) * ||y - X*beta||^2 + alpha * (l1_ratio * ||beta||_1 + 0.5 * (1-l1_ratio) * ||beta||_2^2)
import horizon as hz

result = hz.elastic_net_fit(
    features=[
        [0.5, 0.3, 0.1],
        [0.6, 0.2, 0.4],
        [0.4, 0.5, 0.2],
        [0.7, 0.1, 0.3],
        [0.3, 0.4, 0.5],
    ],
    targets=[0.55, 0.60, 0.50, 0.65, 0.45],
    alpha=0.1,
    l1_ratio=0.5,
    max_iters=1000,
)

print(f"Coefficients: {result.coefficients}")
print(f"Intercept: {result.intercept:.4f}")
print(f"Non-zero features: {result.n_nonzero}")
print(f"R-squared: {result.r_squared:.4f}")
ParameterTypeDefaultDescription
featureslist[list[float]]requiredFeature matrix (n_samples x n_features)
targetslist[float]requiredTarget values (n_samples)
alphafloat0.1Overall regularization strength. Higher = more regularization.
l1_ratiofloat0.5Mix of L1 vs L2 penalty. 1.0 = pure lasso, 0.0 = pure ridge.
max_itersint1000Maximum coordinate descent iterations
tolfloat1e-6Convergence tolerance

ElasticNetResult Type

FieldTypeDescription
coefficientslist[float]Fitted coefficients (one per feature)
interceptfloatFitted intercept term
n_nonzerointNumber of non-zero coefficients (selected features)
r_squaredfloatIn-sample R-squared (coefficient of determination)
msefloatMean squared error on training data
selected_featureslist[int]Indices of features with non-zero coefficients

hz.elastic_net_predict

Generate predictions from a fitted elastic net model.
import horizon as hz

# Fit model
result = hz.elastic_net_fit(features_train, targets_train, alpha=0.1, l1_ratio=0.5)

# Predict on new data
predictions = hz.elastic_net_predict(
    features=features_test,
    coefficients=result.coefficients,
    intercept=result.intercept,
)

for pred, actual in zip(predictions, targets_test):
    print(f"  predicted={pred:.4f}, actual={actual:.4f}")
ParameterTypeDescription
featureslist[list[float]]Feature matrix for prediction (n_samples x n_features)
coefficientslist[float]Fitted coefficients from elastic_net_fit
interceptfloatFitted intercept from elastic_net_fit
Returns list[float]: predicted values.

hz.lasso_fit

Convenience function for pure L1 regularization (elastic net with l1_ratio=1.0). Drives uninformative coefficients to exactly zero.
import horizon as hz

result = hz.lasso_fit(
    features=feature_matrix,
    targets=target_series,
    alpha=0.05,
)

print(f"Selected {result.n_nonzero} of {len(result.coefficients)} features")
for idx in result.selected_features:
    print(f"  Feature {idx}: coeff={result.coefficients[idx]:.4f}")
ParameterTypeDefaultDescription
featureslist[list[float]]requiredFeature matrix (n_samples x n_features)
targetslist[float]requiredTarget values
alphafloat0.1Regularization strength
max_itersint1000Maximum iterations
tolfloat1e-6Convergence tolerance
Returns an ElasticNetResult (same type as elastic_net_fit).

hz.ridge_fit

Convenience function for pure L2 regularization (elastic net with l1_ratio=0.0). Shrinks all coefficients toward zero without eliminating any. Preferred when all features are potentially relevant and multicollinearity is present.
import horizon as hz

result = hz.ridge_fit(
    features=feature_matrix,
    targets=target_series,
    alpha=1.0,
)

print(f"Coefficients: {result.coefficients}")
print(f"R-squared: {result.r_squared:.4f}")
ParameterTypeDefaultDescription
featureslist[list[float]]requiredFeature matrix
targetslist[float]requiredTarget values
alphafloat1.0Regularization strength
max_itersint1000Maximum iterations
tolfloat1e-6Convergence tolerance
Returns an ElasticNetResult.
Ridge regression always keeps all features (n_nonzero equals the total number of features). Use lasso or elastic net when you need automatic feature selection.

hz.elastic_net_cv

Automated regularization parameter selection using k-fold cross-validation. Tests a grid of alpha values and returns the model with the lowest out-of-sample MSE.
import horizon as hz

cv_result = hz.elastic_net_cv(
    features=feature_matrix,
    targets=target_series,
    l1_ratio=0.5,
    n_alphas=50,
    n_folds=5,
)

print(f"Best alpha: {cv_result.best_alpha:.6f}")
print(f"Best CV MSE: {cv_result.best_mse:.6f}")
print(f"Non-zero at best alpha: {cv_result.best_model.n_nonzero}")

# Use the best model directly
predictions = hz.elastic_net_predict(
    features=features_test,
    coefficients=cv_result.best_model.coefficients,
    intercept=cv_result.best_model.intercept,
)
ParameterTypeDefaultDescription
featureslist[list[float]]requiredFeature matrix
targetslist[float]requiredTarget values
l1_ratiofloat0.5L1/L2 mix ratio
n_alphasint50Number of alpha values to test (log-spaced)
n_foldsint5Number of cross-validation folds
max_itersint1000Maximum iterations per fit

CV Result Type

FieldTypeDescription
best_alphafloatAlpha with lowest cross-validation MSE
best_msefloatCross-validation MSE at the best alpha
best_modelElasticNetResultFull model fitted at the best alpha
alphaslist[float]All alpha values tested
cv_mseslist[float]Mean CV MSE at each alpha
cv_stdslist[float]Standard deviation of CV MSE at each alpha

Pipeline Integration

hz.signal_selector

Creates a pipeline function that uses elastic net to select and weight signals from multiple feeds, injecting a composite signal into ctx.params.
import horizon as hz

def composite_quoter(ctx):
    signal = ctx.params.get("signal")
    if signal is None:
        return []

    composite_fair = signal["fair_value"]
    confidence = signal["r_squared"]

    # Scale position size by model confidence
    size = 10 if confidence > 0.3 else 3
    spread = 0.04 if confidence > 0.3 else 0.06

    return hz.quotes(fair=composite_fair, spread=spread, size=size)

hz.run(
    name="signal_selector",
    markets=["election-winner"],
    feeds={
        "poly": hz.PolymarketBook("election-token"),
        "kalshi": hz.KalshiBook("kalshi-event-id"),
        "oracle": hz.ChainlinkFeed("0xabc..."),
    },
    pipeline=[
        hz.signal_selector(
            target_feed="poly",
            signal_feeds=["kalshi", "oracle"],
            lookback=200,
            l1_ratio=0.7,
            retrain_interval=100,
        ),
        composite_quoter,
    ],
)
ParameterTypeDefaultDescription
target_feedstrrequiredFeed representing the target variable
signal_feedslist[str]requiredFeeds to use as features
lookbackint200Observations to retain for training
l1_ratiofloat0.7L1/L2 mix ratio (higher = more feature selection)
retrain_intervalint100Retrain the model every N observations
n_foldsint5Cross-validation folds for alpha selection
param_namestr"signal"Key in ctx.params

Injected Parameters

KeyTypeDescription
ctx.params["signal"]["fair_value"]floatModel-predicted fair value
ctx.params["signal"]["r_squared"]floatIn-sample R-squared of the current model
ctx.params["signal"]["n_signals"]intNumber of non-zero (selected) signal feeds
ctx.params["signal"]["weights"]dict[str, float]Feed name to coefficient mapping

Example: Feature Selection Workflow

import horizon as hz

# Build feature matrix from multiple signal sources
# Each row is one observation; each column is a signal
features = []
targets = []

for t in range(len(prices_target)):
    row = [
        prices_polymarket[t],
        prices_kalshi[t],
        sentiment_score[t],
        volume_ratio[t],
        spread_signal[t],
    ]
    features.append(row)
    targets.append(prices_target[t + 1])  # predict next price

# Cross-validated elastic net
cv = hz.elastic_net_cv(
    features=features,
    targets=targets,
    l1_ratio=0.7,
    n_alphas=50,
    n_folds=5,
)

print(f"Best alpha: {cv.best_alpha:.6f}")
print(f"CV MSE: {cv.best_mse:.6f}")
print(f"Selected features: {cv.best_model.selected_features}")

# Inspect which signals matter
signal_names = ["polymarket", "kalshi", "sentiment", "volume", "spread"]
for idx in cv.best_model.selected_features:
    coeff = cv.best_model.coefficients[idx]
    print(f"  {signal_names[idx]}: {coeff:+.4f}")

Mathematical Background

The elastic net minimizes:L(beta) = (1/2n) * ||y - X*beta||^2 + alpha * [l1_ratio * ||beta||_1 + 0.5 * (1 - l1_ratio) * ||beta||_2^2]When l1_ratio=1, this is the lasso (L1 only). When l1_ratio=0, this is ridge regression (L2 only). The L1 term produces sparsity (feature selection); the L2 term handles correlated features and improves numerical stability.
Horizon solves the elastic net using cyclic coordinate descent. For each feature j, the update is:beta_j = soft_threshold(partial_residual_j, alpha * l1_ratio) / (1 + alpha * (1 - l1_ratio))where soft_threshold(z, gamma) = sign(z) * max(|z| - gamma, 0). The algorithm cycles through all features until convergence.
The regularization strength alpha controls the bias-variance tradeoff. K-fold CV splits the data into K folds, trains on K-1, and evaluates on the held-out fold. The alpha with the lowest average MSE across folds is selected. The alpha grid is log-spaced from alpha_max (where all coefficients are zero) down to alpha_max / 1000.
Features should be standardized (zero mean, unit variance) before fitting elastic net. Unstandardized features with different scales will cause the regularization to penalize large-scale features disproportionately.