---
title: "Horizon Feeds"
description: "Live price data feeds for Horizon: Binance, Polymarket, Kalshi, PredictIt, Manifold, ESPN, NWS, and custom REST with JSON path extraction."
icon: "rss"
---

Feeds provide live price data to your pipeline functions via `ctx.feeds`. Each feed runs as an async task on the engine's tokio runtime.

## Available Feeds

<CardGroup cols={2}>
  <Card title="BinanceWS" icon="bolt">
    Real-time trade stream from Binance WebSocket.
  </Card>
  <Card title="PolymarketBook" icon="book-open">
    Polymarket orderbook snapshots via WebSocket.
  </Card>
  <Card title="KalshiBook" icon="chart-bar">
    Kalshi orderbook polling via REST.
  </Card>
  <Card title="RESTFeed" icon="globe">
    Generic REST API polling for any endpoint.
  </Card>
  <Card title="PredictItFeed" icon="flag">
    PredictIt market contract prices.
  </Card>
  <Card title="ManifoldFeed" icon="users">
    Manifold Markets crowd probabilities.
  </Card>
  <Card title="ESPNFeed" icon="futbol">
    Live sports scores from ESPN.
  </Card>
  <Card title="NWSFeed" icon="cloud-sun-rain">
    National Weather Service forecasts and alerts.
  </Card>
  <Card title="RESTJsonPathFeed" icon="code">
    Any REST API with dot-notation JSON path extraction.
  </Card>
  <Card title="ChainlinkFeed" icon="link">
    On-chain Chainlink price oracle via JSON-RPC.
  </Card>
</CardGroup>

## BinanceWS

Real-time price stream from Binance via WebSocket:

```python
hz.run(
    feeds={"btc": hz.BinanceWS("btcusdt")},
    ...
)

def fair_value(ctx: hz.Context) -> float:
    return ctx.feeds["btc"].price
```

The feed connects to Binance's trade stream and updates `price`, `bid`, `ask`, and `timestamp` on each trade.

| Field | Description |
|-------|-------------|
| `symbol` | Binance trading pair (e.g., `"btcusdt"`, `"ethusdt"`) |

## PolymarketBook

Polymarket orderbook feed via WebSocket:

```python
hz.run(
    feeds={"poly": hz.PolymarketBook("will-btc-hit-100k")},
    ...
)

def fair_value(ctx: hz.Context) -> float:
    poly = ctx.feeds["poly"]
    return (poly.bid + poly.ask) / 2
```

| Field | Description |
|-------|-------------|
| `market_slug` | Polymarket market slug |

## KalshiBook

Kalshi orderbook feed via REST polling:

```python
hz.run(
    feeds={"kalshi": hz.KalshiBook("KXBTC-25FEB16")},
    ...
)
```

| Field | Description |
|-------|-------------|
| `ticker` | Kalshi market ticker (e.g., `"KXBTC-25FEB16"`) |

## RESTFeed

Generic REST API polling feed for any JSON endpoint:

```python
hz.run(
    feeds={"custom": hz.RESTFeed("https://api.example.com/price", interval=5.0)},
    ...
)
```

| Field | Default | Description |
|-------|---------|-------------|
| `url` | required | REST endpoint URL |
| `interval` | `5.0` | Polling interval in seconds |

## PredictItFeed

PredictIt market contract prices via REST polling:

```python
hz.run(
    feeds={"election": hz.PredictItFeed(market_id=7456, contract_id=28562)},
    ...
)

def fair_value(ctx: hz.Context) -> float:
    pi = ctx.feeds["election"]
    return pi.price  # lastTradePrice
```

| Field | Default | Description |
|-------|---------|-------------|
| `market_id` | required | PredictIt market ID (integer) |
| `contract_id` | `None` | Specific contract ID. If `None`, uses the first contract. |
| `interval` | `5.0` | Polling interval in seconds (minimum 1s) |

**Snapshot mapping**: `price` = lastTradePrice, `bid` = bestBuyYesCost, `ask` = bestSellYesCost.

Handles HTTP 429 with automatic exponential backoff.

## ManifoldFeed

Manifold Markets probability feed via REST polling:

```python
hz.run(
    feeds={"manifold": hz.ManifoldFeed("will-btc-hit-100k-by-2026")},
    ...
)

def fair_value(ctx: hz.Context) -> float:
    return ctx.feeds["manifold"].price  # probability (0-1)
```

| Field | Default | Description |
|-------|---------|-------------|
| `slug` | required | Manifold market slug |
| `interval` | `5.0` | Polling interval in seconds |

**Snapshot mapping**: `price` = probability, `volume_24h` = volume.

## ESPNFeed

Live sports scores from ESPN's scoreboard API:

```python
hz.run(
    feeds={"nba": hz.ESPNFeed("basketball", "nba")},
    ...
)

def score_signal(ctx: hz.Context) -> float:
    game = ctx.feeds["nba"]
    home_score = game.price
    away_score = game.bid
    period = game.volume_24h
    # game.source contains "espn:basketball:nba:{event_id}:{status}"
    return home_score - away_score
```

| Field | Default | Description |
|-------|---------|-------------|
| `sport` | required | ESPN sport name (e.g., `"basketball"`, `"football"`) |
| `league` | required | ESPN league name (e.g., `"nba"`, `"nfl"`) |
| `event_id` | `None` | Filter to a specific game. If `None`, picks the first active game. |
| `interval` | `10.0` | Polling interval in seconds |

**Snapshot mapping**: `price` = home score, `bid` = away score, `volume_24h` = period/quarter. The `source` field encodes the game status (`"in_progress"`, `"scheduled"`, `"final"`).

## NWSFeed

National Weather Service data in two modes:

### Forecast mode (default)

```python
hz.run(
    feeds={"weather": hz.NWSFeed(office="TOP", grid_x=31, grid_y=80)},
    ...
)

def weather_signal(ctx: hz.Context) -> float:
    wx = ctx.feeds["weather"]
    temperature = wx.price
    wind_speed = wx.bid
    precip_chance = wx.ask
    return precip_chance / 100.0
```

### Alerts mode

```python
hz.run(
    feeds={"alerts": hz.NWSFeed(state="FL", mode="alerts")},
    ...
)

def alert_signal(ctx: hz.Context) -> float:
    al = ctx.feeds["alerts"]
    has_alerts = al.price  # 1.0 if active alerts, 0.0 otherwise
    alert_count = al.bid
    # al.source contains severity: "nws:alerts:FL:Severe"
    return has_alerts
```

| Field | Default | Description |
|-------|---------|-------------|
| `office` | `""` | NWS office code (e.g., `"TOP"`, `"MIA"`). Required for forecast mode. |
| `grid_x` | `0` | Grid X coordinate. Required for forecast mode. |
| `grid_y` | `0` | Grid Y coordinate. Required for forecast mode. |
| `state` | `""` | US state abbreviation (e.g., `"FL"`). Required for alerts mode. |
| `mode` | `"forecast"` | `"forecast"` or `"alerts"` |
| `user_agent` | `"Horizon-SDK/0.4.5"` | User-Agent header (NWS requires one) |
| `interval` | `60.0` | Polling interval in seconds (minimum 60s per NWS policy) |

**Forecast mapping**: `price` = temperature, `bid` = wind speed, `ask` = precipitation chance.

**Alerts mapping**: `price` = 1.0 if active alerts else 0.0, `bid` = alert count. Source encodes severity.

## RESTJsonPathFeed

Flexible REST feed that extracts values from any JSON API using dot-notation paths:

```python
hz.run(
    feeds={
        "btc_coingecko": hz.RESTJsonPathFeed(
            url="https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd",
            price_path="bitcoin.usd",
        ),
    },
    ...
)
```

Path resolution:
- `"price"` → `json["price"]`
- `"data.market.price"` → `json["data"]["market"]["price"]`
- `"markets.0.lastPrice"` → `json["markets"][0]["lastPrice"]`
- Numeric segments try array index first, then object key
- String values are auto-parsed to `f64`

| Field | Default | Description |
|-------|---------|-------------|
| `url` | required | REST endpoint URL |
| `price_path` | `None` | Dot-notation path to price field. Falls back to `json["price"]`. |
| `bid_path` | `None` | Dot-notation path to bid field. Falls back to `json["bid"]`. |
| `ask_path` | `None` | Dot-notation path to ask field. Falls back to `json["ask"]`. |
| `volume_path` | `None` | Dot-notation path to volume field. Falls back to `json["volume"]`. |
| `interval` | `5.0` | Polling interval in seconds |

<Tip>
  `RESTJsonPathFeed` is the most flexible feed — use it to pull data from any public API (CoinGecko, weather services, sports APIs, etc.) without writing custom feed code.
</Tip>

## ChainlinkFeed

On-chain Chainlink price oracle feed. Reads `latestRoundData()` from any Chainlink aggregator proxy contract via JSON-RPC `eth_call`. Works with any EVM chain — Ethereum, Arbitrum, Polygon, BSC, etc.

```python
hz.run(
    feeds={
        "eth_usd": hz.ChainlinkFeed(
            contract_address="0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419",
            rpc_url="https://eth.llamarpc.com",
        ),
    },
    ...
)

def fair_value(ctx: hz.Context) -> float:
    return ctx.feeds["eth_usd"].price  # e.g., 3245.12
```

| Field | Default | Description |
|-------|---------|-------------|
| `contract_address` | required | Chainlink aggregator proxy address (with `0x` prefix) |
| `rpc_url` | required | JSON-RPC endpoint URL for the target chain |
| `decimals` | `8` | Price decimals (most Chainlink feeds use 8; some use 18) |
| `interval` | `10.0` | Polling interval in seconds (minimum 1s) |

**Snapshot mapping**: `price` = on-chain price (divided by `10^decimals`), `timestamp` = on-chain `updatedAt`.

**Common contract addresses** (Ethereum mainnet):

| Pair | Address |
|------|---------|
| ETH/USD | `0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419` |
| BTC/USD | `0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c` |
| LINK/USD | `0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c` |
| USDC/USD | `0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6` |

<Tip>
  Find contract addresses for any chain at [data.chain.link](https://data.chain.link). Chainlink feeds work on Ethereum, Arbitrum, Polygon, BSC, Avalanche, and more — just change the `rpc_url`.
</Tip>

## Feed Data in Context

Each feed is accessible in pipeline functions via `ctx.feeds`:

```python
from horizon.context import FeedData

def fair_value(ctx: hz.Context) -> float:
    btc = ctx.feeds.get("btc", FeedData())

    btc.price       # Last trade price
    btc.bid         # Best bid
    btc.ask         # Best ask
    btc.timestamp   # Unix timestamp of last update

    return btc.price
```

## FeedSnapshot (Engine Level)

At the Engine level, feed data is stored as `FeedSnapshot` objects:

```python
snap = engine.feed_snapshot("btc")

snap.price       # 100250.0
snap.bid         # 100248.0
snap.ask         # 100252.0
snap.timestamp   # 1700000000.0
snap.source      # "binance_ws"
snap.volume_24h  # 50000.0
```

Other Engine feed methods:

```python
all_snaps = engine.all_feed_snapshots()  # dict[str, FeedSnapshot]
age = engine.feed_age("btc")            # seconds since last update (or None)
engine.stop_feeds()                      # stop all feeds
```

## Feed Staleness

In live mode, the strategy loop checks feed staleness before quoting. If any feed is stale (no update within the threshold), quoting is skipped for that cycle:

```python
# Default threshold: 30 seconds
# Override via params:
hz.run(
    params={"feed_stale_threshold": 60.0},
    ...
)
```

The `FeedData.is_stale()` method checks staleness:

```python
from horizon.context import FeedData

feed = ctx.feeds.get("btc", FeedData())
if feed.is_stale(max_age_secs=30.0):
    # Feed hasn't updated, skip quoting
    return []
```

## Engine-Level Feed Management

When using the Engine directly, start feeds with `start_feed()`:

```python
# Original feeds
engine.start_feed("btc", "binance_ws", symbol="btcusdt")
engine.start_feed("poly", "polymarket_book", symbol="will-btc-hit-100k")
engine.start_feed("kalshi", "kalshi_book", symbol="KXBTC-25FEB16")
engine.start_feed("custom", "rest", url="https://api.example.com/price", interval=5.0)

# New feeds (v0.4.5) — use config_json for structured config
import json

engine.start_feed("pi", "predictit", config_json=json.dumps({
    "market_id": 7456, "contract_id": 28562
}))
engine.start_feed("mf", "manifold", config_json=json.dumps({
    "slug": "will-btc-hit-100k-by-2026"
}))
engine.start_feed("nba", "espn", config_json=json.dumps({
    "sport": "basketball", "league": "nba"
}))
engine.start_feed("wx", "nws", config_json=json.dumps({
    "mode": "alerts", "state": "FL"
}))
engine.start_feed("btc2", "rest_json_path", config_json=json.dumps({
    "url": "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd",
    "price_path": "bitcoin.usd"
}))
engine.start_feed("eth_usd", "chainlink", config_json=json.dumps({
    "contract_address": "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419",
    "rpc_url": "https://eth.llamarpc.com"
}))
```

## Multiple Feeds

Use multiple feeds to combine data sources across markets:

```python
hz.run(
    feeds={
        # Crypto price
        "btc": hz.BinanceWS("btcusdt"),
        # Prediction market orderbooks
        "poly": hz.PolymarketBook("will-btc-hit-100k"),
        "kalshi": hz.KalshiBook("KXBTC-25FEB16"),
        # Cross-market signals
        "pi_election": hz.PredictItFeed(market_id=7456, contract_id=28562),
        "manifold": hz.ManifoldFeed("will-btc-hit-100k-by-2026"),
        # Sports data for sports-related markets
        "nba": hz.ESPNFeed("basketball", "nba"),
        # Weather for hurricane/climate markets
        "weather_fl": hz.NWSFeed(state="FL", mode="alerts"),
        # Any custom API
        "custom": hz.RESTJsonPathFeed(
            url="https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd",
            price_path="bitcoin.usd",
        ),
        # On-chain Chainlink oracle
        "eth_usd": hz.ChainlinkFeed(
            contract_address="0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419",
            rpc_url="https://eth.llamarpc.com",
        ),
    },
    ...
)

def fair_value(ctx: hz.Context) -> float:
    from horizon.context import FeedData
    btc = ctx.feeds.get("btc", FeedData())
    poly = ctx.feeds.get("poly", FeedData())

    # Combine BTC spot price with Polymarket book
    if poly.bid > 0 and poly.ask > 0:
        return (poly.bid + poly.ask) / 2
    return btc.price * 0.01  # Fallback
```
