Skip to main content

Market Discovery

Horizon provides built-in market discovery for Polymarket, Kalshi, Limitless, Alpaca, and IBKR. Search by keyword, filter by volume or category, and get back Market objects ready to use with hz.run().
Prediction Markets: Polymarket, Kalshi, Limitless Equities & Options: Alpaca, IBKR Crypto: Coinbase, Robinhood
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.

Overview

Search Markets

hz.discover_markets() searches by keyword with volume, category, and sort filters.

Top Markets

hz.top_markets() returns the highest-volume active markets.

Discover Events

hz.discover_events() finds multi-outcome events with all outcomes populated.

All Exchanges

Works with Polymarket, Kalshi, Limitless, Alpaca, and IBKR.

hz.discover_markets

Search for markets across exchanges.
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

ParameterTypeDefaultDescription
exchangestr"polymarket""polymarket", "kalshi", "limitless", "alpaca", or "ibkr"
querystr""Search text (market title/slug/ticker)
activeboolTrueOnly return active (open) markets
limitint20Max number of results
min_volumefloat0.0Minimum 24h volume filter (Polymarket only)
categorystr""Category/tag filter (exchange-specific)
sort_bystr"volume"Sort order

Sort Options

ValueDescription
"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
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.
Alpaca:
  • Searches the Alpaca assets API (/v2/assets) for US equities
  • query matches symbol or company name (client-side)
  • Returns Market objects with exchange="alpaca" and asset_class="equity"
  • Requires APCA_API_KEY_ID and APCA_API_SECRET_KEY environment variables
IBKR:
  • Searches via the IBKR symbol search endpoint
  • query matches symbol or company name
  • Returns Market objects with exchange="ibkr" and contract ID as the market ID
  • Requires IBKR Client Portal or TWS running locally

hz.top_markets

Convenience wrapper to get the highest-volume active markets.
# 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)
ParameterTypeDefaultDescription
exchangestr"polymarket"Exchange to query
limitint10Number of markets
categorystr""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.
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()
ParameterTypeDefaultDescription
exchangestr"polymarket"Currently only "polymarket" supported
querystr""Search text
activeboolTrueOnly active events
limitint10Max events
min_volumefloat0.0Minimum total volume across outcomes
Event discovery only returns events with 2+ outcomes. Single-outcome binary markets are returned by discover_markets() instead.

Using Discovered Markets with hz.run

The main purpose of discovery is to get Market objects for trading:
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

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

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

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

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

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 ***")

Equity Discovery

import horizon as hz

# Search for stocks on Alpaca
stocks = hz.discover_markets(exchange="alpaca", query="AAPL", limit=5)
for s in stocks:
    print(f"{s.ticker}: {s.name} (asset_class={s.asset_class})")

# Search IBKR for options
contracts = hz.discover_markets(exchange="ibkr", query="AAPL", limit=10)
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.