Cross-Asset Strategies

Strategies that trade one asset class while reading features from another

One of Horizon’s most powerful patterns: a strategy that trades one asset class while reading features from another. Used for hedging, regime filtering, and cross-asset arbitrage.

The technique

Features accept a market argument that pins them to a specific market id, regardless of the strategy’s current universe. That lets you read the VIX from an equity strategy, or a prediction market probability from a crypto strategy, or a bond yield curve from an options strategy.

Example: Equity hedged by prediction market

python
from horizon import Strategy, Signal
from horizon.asset_classes import AssetClass, Equity
from horizon.features import Price, Zscore

class RecessionHedge(Strategy):
    asset_classes = [Equity]          # only trades equities
    features = {
        "recession_prob": Price(market="polymarket:recession-2025"),    # pinned
        "z": Zscore(window=20),                                          # per-market
    }

    def evaluate(self, f, universe):
        # Same value for every market. it's the recession market's price
        recession_prob = next(iter(f.recession_prob.values()))

        if recession_prob > 0.60:
            return []  # pause on high recession risk

        return [
            Signal.from_score(m, score=-f.z[m.id], edge_per_stdev=15)
            for m in universe
            if abs(f.z[m.id]) > 2
        ]

Example: VIX-regime equity strategy

python
class VIXAware(Strategy):
    asset_classes = [Equity]
    features = {
        "vix": Price(market="CBOE:VIX"),
        "z": Zscore(window=20),
    }

    def evaluate(self, f, universe):
        vix = next(iter(f.vix.values()))

        # Different entry rules per regime
        if vix < 15:
            # Low vol. quiet regime, lower z-threshold
            threshold = 1.5
        elif vix < 25:
            # Normal
            threshold = 2.0
        else:
            # High vol. wait for deeper pullbacks
            threshold = 3.0

        return [
            Signal.from_score(m, score=-f.z[m.id], edge_per_stdev=15)
            for m in universe
            if abs(f.z[m.id]) > threshold
        ]

Example: Crypto-correlated equity strategy

python
class CryptoCorrelated(Strategy):
    asset_classes = [Equity]
    features = {
        "btc_return": Return(window=5, market="BINANCE:BTC-USD"),
        "my_return": Return(window=5),
    }

    def evaluate(self, f, universe):
        btc = next(iter(f.btc_return.values()))

        # Only go long if BTC is up (correlation play)
        if btc < 0:
            return []

        return [
            Signal.increase(m, edge_bps=30, reason=f"btc={btc:+.2%}")
            for m in universe
            if f.my_return[m.id] > 0.01
        ]

How pinning works

When Feature(market="X") is specified:

  • The feature always uses market “X“‘s history, regardless of the current market
  • All strategies’ universe markets see the same value from that feature
  • The value propagates through ctx.feeds to downstream consumers

In the feature store’s compute method:

python
for mid in universe_market_ids:
    target_mid = feature.market or mid   # use pinned market if set
    hist = self.history(target_mid)
    value = feature.compute(target_mid, hist, feeds)

When feature.market == "CBOE:VIX", every iteration uses VIX’s history, so every market sees the same VIX value.

Combining pinned and per-market features

python
features = {
    "vix": Price(market="CBOE:VIX"),           # pinned
    "z": Zscore(window=20),                      # per-market
    "spy_return_5d": Return(window=5, market="SPY"),   # pinned
}

Mix freely: the pinned features don’t interfere with the per-market ones.

Caveats

Next