---
title: "Market Discovery"
description: "Search and discover markets on Polymarket and Kalshi. Find markets by keyword, category, volume, and more. Returns Market objects ready for hz.run()."
icon: "magnifying-glass"
---

# Market Discovery

Horizon provides built-in market discovery for both Polymarket and Kalshi. Search by keyword, filter by volume or category, and get back `Market` objects ready to use with `hz.run()`.

<Note>
  Discovery queries are public API calls  - no exchange credentials needed. The returned `Market` objects include token IDs, condition IDs, and all metadata needed for trading.
</Note>

## Overview

<CardGroup cols={2}>
  <Card title="Search Markets" icon="magnifying-glass">
    `hz.discover_markets()` searches by keyword with volume, category, and sort filters.
  </Card>
  <Card title="Top Markets" icon="trophy">
    `hz.top_markets()` returns the highest-volume active markets.
  </Card>
  <Card title="Discover Events" icon="calendar">
    `hz.discover_events()` finds multi-outcome events with all outcomes populated.
  </Card>
  <Card title="Both Exchanges" icon="arrows-split-up-and-left">
    Works with both Polymarket (Gamma API) and Kalshi (public API).
  </Card>
</CardGroup>

---

## hz.discover_markets

Search for markets on Polymarket or Kalshi.

```python
import horizon as hz

# Search Polymarket for election markets
markets = hz.discover_markets(
    exchange="polymarket",
    query="election",
    active=True,
    limit=10,
    min_volume=10000.0,
    sort_by="volume",
)

for m in markets:
    print(f"{m.name}")
    print(f"  ID: {m.id}")
    print(f"  Token IDs: yes={m.yes_token_id}, no={m.no_token_id}")
    print(f"  Condition: {m.condition_id}")
```

### Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `exchange` | `str` | `"polymarket"` | `"polymarket"` or `"kalshi"` |
| `query` | `str` | `""` | Search text (market title/slug/ticker) |
| `active` | `bool` | `True` | Only return active (open) markets |
| `limit` | `int` | `20` | Max number of results |
| `min_volume` | `float` | `0.0` | Minimum 24h volume filter (Polymarket only) |
| `category` | `str` | `""` | Category/tag filter (exchange-specific) |
| `sort_by` | `str` | `"volume"` | Sort order |

### Sort Options

| Value | Description |
|-------|-------------|
| `"volume"` | 24h trading volume (default) |
| `"newest"` | Most recently created |
| `"liquidity"` | Deepest orderbook liquidity |
| `""` | No specific ordering |

### Exchange-Specific Behavior

**Polymarket:**
- `query` matches market question text and slugs (client-side filtering on `/markets` endpoint)
- `category` uses server-side tag filtering via the `/events` endpoint (e.g., `"crypto"`, `"politics"`, `"sports"`). Returns all markets from matching events. This is the recommended way to browse by topic.
- `min_volume` filters markets below the USD threshold
- Returns `Market` objects with `yes_token_id`, `no_token_id`, and `condition_id`

**Kalshi:**
- `query` with an uppercase ticker prefix (e.g., `"KXBTC"`) uses server-side series filtering for fast results. Lowercase text queries use client-side matching over paginated results, which may miss markets deep in the listing.
- `category` is the recommended way to search Kalshi. Maps to series tickers (e.g., `"KXBTC"` for all BTC markets, `"KXETH"` for ETH, `"INX"` for S&P 500).
- `min_volume` is not supported (Kalshi API limitation)
- Returns `Market` objects with `ticker` field

<Tip>
  For Kalshi, prefer `category` over `query` for reliable results. Kalshi's API doesn't support server-side text search, so text queries only scan a limited number of markets.
</Tip>

---

## hz.top_markets

Convenience wrapper to get the highest-volume active markets.

```python
# Top 10 Polymarket markets by volume
markets = hz.top_markets(limit=10)

# Top Kalshi markets in crypto category
markets = hz.top_markets(exchange="kalshi", category="KXBTC", limit=5)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `exchange` | `str` | `"polymarket"` | Exchange to query |
| `limit` | `int` | `10` | Number of markets |
| `category` | `str` | `""` | Category filter |

Equivalent to `discover_markets(active=True, sort_by="volume", ...)`.

---

## hz.discover_events

Discover multi-outcome events from Polymarket. Returns `Event` objects with all outcomes populated, including YES/NO token IDs and current prices.

```python
events = hz.discover_events(
    query="election",
    active=True,
    limit=5,
    min_volume=50000.0,
)

for event in events:
    print(f"\n{event.name} ({event.outcome_count()} outcomes)")
    for outcome in event.outcomes:
        print(f"  {outcome.name}: {outcome.yes_price:.2f}")

    # Convert to Market objects for trading
    markets = event.to_markets()
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `exchange` | `str` | `"polymarket"` | Currently only `"polymarket"` supported |
| `query` | `str` | `""` | Search text |
| `active` | `bool` | `True` | Only active events |
| `limit` | `int` | `10` | Max events |
| `min_volume` | `float` | `0.0` | Minimum total volume across outcomes |

<Note>
  Event discovery only returns events with 2+ outcomes. Single-outcome binary markets are returned by `discover_markets()` instead.
</Note>

---

## Using Discovered Markets with hz.run

The main purpose of discovery is to get `Market` objects for trading:

```python
import horizon as hz

# Discover a market
markets = hz.discover_markets(query="bitcoin", limit=1)
market = markets[0]

# Use it directly in hz.run()
def fair_value(ctx):
    return ctx.feeds["book"].price or 0.5

def quoter(ctx, fair):
    return hz.quotes(fair, spread=0.04, size=5)

hz.run(
    name="discovered_strategy",
    markets=[market],
    feeds={"book": hz.PolymarketBook(market.id)},
    pipeline=[fair_value, quoter],
    risk=hz.Risk(max_position=100),
)
```

### Using Discovered Events

```python
import horizon as hz

events = hz.discover_events(query="bitcoin", limit=1)
event = events[0]

# Register the event and trade all outcomes
markets = event.to_markets()
market_ids = [m.id for m in markets]

hz.run(
    name="event_strategy",
    markets=markets,
    feeds={m.id: hz.PolymarketBook(m.id) for m in markets},
    pipeline=[fair_value, quoter],
    risk=hz.Risk(max_position=100),
)
```

---

## Examples

### Scanning for High-Volume Opportunities

```python
import horizon as hz

# Find liquid markets on both exchanges
poly_markets = hz.top_markets("polymarket", limit=20)
kalshi_markets = hz.top_markets("kalshi", limit=20)

print("=== Polymarket ===")
for m in poly_markets:
    print(f"  {m.name[:60]} ({m.id})")

print("\n=== Kalshi ===")
for m in kalshi_markets:
    print(f"  {m.name[:60]} ({m.ticker})")
```

### Category-Based Discovery

```python
import horizon as hz

# Polymarket categories: politics, crypto, sports, etc.
crypto_markets = hz.discover_markets(
    exchange="polymarket",
    category="crypto",
    min_volume=5000,
    limit=10,
)

# Kalshi series: KXBTC, KXETH, etc.
btc_markets = hz.discover_markets(
    exchange="kalshi",
    category="KXBTC",
    limit=10,
)
```

### Finding Cross-Exchange Pairs

```python
import horizon as hz

# Find matching markets on both exchanges
poly = hz.discover_markets("polymarket", query="bitcoin", limit=5)
kalshi = hz.discover_markets("kalshi", query="KXBTC", limit=5)

print("Polymarket:")
for m in poly:
    print(f"  {m.id}: {m.name}")

print("Kalshi:")
for m in kalshi:
    print(f"  {m.ticker}: {m.name}")

# Use for cross-exchange arb
# hz.run(markets=[poly[0], kalshi[0]], exchanges=[...], ...)
```

### Event Parity Check

```python
import horizon as hz

events = hz.discover_events(limit=5, min_volume=100000)

for event in events:
    prices = [o.yes_price for o in event.outcomes]
    total = sum(prices)
    print(f"{event.name}")
    print(f"  Outcomes: {event.outcome_count()}")
    print(f"  Price sum: {total:.3f} (should be ~1.0)")
    if abs(total - 1.0) > 0.03:
        print(f"  *** Potential arb: {abs(total - 1.0):.3f} overround ***")
```

<Warning>
  Discovery results depend on exchange API availability. If the Polymarket Gamma API or Kalshi API is down, functions return empty lists and log an error. Always check the returned list length before indexing.
</Warning>
