# Horizon SDK — Strategy Examples

These are complete, runnable strategy patterns. Use them as templates.

---

## 1. Simple Threshold Strategy

Buy YES when your probability estimate exceeds market price by a threshold.

```python
import horizon as hz

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

def signal(ctx: hz.Context, fair: float) -> float:
    # Your edge: you think true prob is 5% higher than market
    return fair + 0.05

def quoter(ctx: hz.Context, signal: float) -> list[hz.Quote]:
    return hz.quotes(fair=signal, spread=0.04, size=10)

hz.run(
    name="threshold_buyer",
    markets=["will-btc-hit-100k"],
    feeds={"poly": hz.PolymarketBook("will-btc-hit-100k")},
    pipeline=[fair_value, signal, quoter],
    risk=hz.Risk(max_position=100, max_notional=500),
)
```

---

## 2. Market Making with Inventory Skew

Classic Avellaneda-Stoikov: widen spread and skew quotes based on inventory.

```python
import horizon as hz

def fair_value(ctx: hz.Context) -> float:
    feed = ctx.feeds["book"]
    if feed.bid > 0 and feed.ask > 0:
        return (feed.bid + feed.ask) / 2
    return feed.price

def skew(ctx: hz.Context, fair: float) -> float:
    inventory = ctx.inventory.net_for_market(ctx.market_id)
    gamma = ctx.params.get("gamma", 0.1)
    return hz.reservation_price(fair, inventory, gamma)

def spread(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    inventory = ctx.inventory.net_for_market(ctx.market_id)
    gamma = ctx.params.get("gamma", 0.1)
    half_spread = hz.optimal_spread(fair, inventory, gamma)
    bid = max(0.01, fair - half_spread)
    ask = min(0.99, fair + half_spread)
    if bid >= ask:
        return []
    return [hz.Quote(bid=bid, ask=ask, size=5)]

hz.run(
    name="mm_with_skew",
    markets=["will-btc-hit-100k"],
    feeds={"book": hz.PolymarketBook("will-btc-hit-100k")},
    pipeline=[fair_value, skew, spread],
    risk=hz.Risk(max_position=50, max_notional=500, max_drawdown_pct=5),
    params={"gamma": 0.1},
)
```

---

## 3. Multi-Feed Fair Value

Combine multiple data sources to form a fair value estimate.

```python
import horizon as hz

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

    # Blend: 60% orderbook mid, 40% external price signal
    book_mid = 0.5
    if book and book.bid > 0 and book.ask > 0:
        book_mid = (book.bid + book.ask) / 2

    external = 0.5
    if btc and btc.price > 0:
        # Map BTC price to probability (example: >100k = YES likely)
        external = min(0.95, max(0.05, btc.price / 120000))

    return 0.6 * book_mid + 0.4 * external

def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    return hz.quotes(fair=fair, spread=0.06, size=5)

hz.run(
    name="multi_feed_mm",
    markets=["will-btc-hit-100k"],
    feeds={
        "btc": hz.BinanceWS("btcusdt"),
        "book": hz.PolymarketBook("will-btc-hit-100k"),
    },
    pipeline=[fair_value, quoter],
    risk=hz.Risk(max_position=100, max_notional=1000),
)
```

---

## 4. Multi-Outcome Event Trading

Trade all outcomes of an event using the same pipeline.

```python
import horizon as hz

event = hz.Event(
    id="2024-election",
    name="2024 US Presidential Election",
    outcomes=[
        hz.Outcome(market_id="trump-wins", name="Trump"),
        hz.Outcome(market_id="harris-wins", name="Harris"),
        hz.Outcome(market_id="other-wins", name="Other"),
    ],
)

def fair_value(ctx: hz.Context) -> float:
    """Equal-weight prior, adjusted by feed."""
    base = 1.0 / 3.0  # 3 outcomes
    feed = ctx.feeds.get(ctx.market_id)
    if feed and feed.price > 0:
        return 0.5 * base + 0.5 * feed.price
    return base

def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    return hz.quotes(fair=fair, spread=0.04, size=10)

hz.run(
    name="election_mm",
    events=[event],
    feeds={
        "trump-wins": hz.PolymarketBook("trump-wins-2024"),
        "harris-wins": hz.PolymarketBook("harris-wins-2024"),
        "other-wins": hz.PolymarketBook("other-wins-2024"),
    },
    pipeline=[fair_value, quoter],
    risk=hz.Risk(max_position=50, max_notional=500),
)
```

---

## 5. Kelly-Sized Entry

Use Kelly criterion for optimal position sizing.

```python
import horizon as hz

def estimate_prob(ctx: hz.Context) -> float:
    """Your model's probability estimate."""
    feed = ctx.feed
    # Example: simple momentum signal
    return min(0.95, max(0.05, feed.price + 0.03))

def size_position(ctx: hz.Context, estimate_prob: float) -> list[hz.Quote]:
    price = ctx.feed.price
    if price <= 0.01 or price >= 0.99:
        return []

    # Kelly sizing
    size = hz.kelly_size(
        prob=estimate_prob,
        market_price=price,
        bankroll=1000.0,
        fraction=0.25,  # Quarter Kelly for safety
        max_size=50.0,
    )

    if size < 1.0:
        return []  # No edge or too small

    # Only submit buy side where we have edge
    if estimate_prob > price:
        return [hz.Quote(bid=price, ask=0.0, size=size)]
    else:
        return [hz.Quote(bid=0.0, ask=price, size=size)]

hz.run(
    name="kelly_entry",
    markets=["will-btc-hit-100k"],
    feeds={"default": hz.PolymarketBook("will-btc-hit-100k")},
    pipeline=[estimate_prob, size_position],
    risk=hz.Risk(max_position=100, max_notional=1000),
)
```

---

## 6. Dict Pipeline (Per-Market Logic)

Different strategies for different markets.

```python
import horizon as hz

def crypto_fair(ctx: hz.Context) -> float:
    btc = ctx.feeds.get("btc")
    return btc.price / 120000 if btc and btc.price > 0 else 0.5

def politics_fair(ctx: hz.Context) -> float:
    feed = ctx.feeds.get(ctx.market_id)
    return feed.price if feed and feed.price > 0 else 0.5

def tight_quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    return hz.quotes(fair=fair, spread=0.02, size=10)

def wide_quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    return hz.quotes(fair=fair, spread=0.06, size=5)

hz.run(
    name="multi_market",
    markets=["btc-100k", "election-winner", "weather-event"],
    feeds={
        "btc": hz.BinanceWS("btcusdt"),
        "btc-100k": hz.PolymarketBook("btc-100k"),
        "election-winner": hz.PolymarketBook("election-winner"),
        "weather-event": hz.PolymarketBook("weather-event"),
    },
    pipeline={
        "btc-100k": [crypto_fair, tight_quoter],      # Tight spreads for crypto
        "election-winner": [politics_fair, wide_quoter], # Wide spreads for politics
        "*": [politics_fair, wide_quoter],               # Default fallback
    },
    risk=hz.Risk(max_position=50, max_notional=500),
)
```

---

## 7. Feed Staleness Guard

Skip quoting when data is stale.

```python
import horizon as hz

def guarded_fair(ctx: hz.Context) -> float:
    feed = ctx.feed
    if feed.is_stale(max_age_secs=30):
        return -1.0  # Signal: don't quote

    return (feed.bid + feed.ask) / 2 if feed.bid > 0 else feed.price

def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    if fair < 0:
        return []  # Stale feed, pull quotes
    return hz.quotes(fair=fair, spread=0.04, size=10)

hz.run(
    name="guarded_mm",
    markets=["my-market"],
    feeds={"default": hz.PolymarketBook("my-market")},
    pipeline=[guarded_fair, quoter],
    risk=hz.Risk(max_position=100, max_notional=500),
)
```

---

## 8. Stop-Loss & Take-Profit Brackets

Use contingent orders for risk management.

```python
import horizon as hz

def entry_signal(ctx: hz.Context) -> list[hz.Quote]:
    feed = ctx.feed
    if feed.price < 0.40:
        # Cheap, buy YES
        return [hz.Quote(bid=feed.price, ask=0.0, size=20)]
    return []

hz.run(
    name="bracket_strategy",
    markets=["my-market"],
    feeds={"default": hz.PolymarketBook("my-market")},
    pipeline=[entry_signal],
    risk=hz.Risk(max_position=50, max_notional=500),
)

# After starting, you can add contingent orders via MCP or tools:
# engine.add_stop_loss(market_id="my-market", side="yes", order_side="sell",
#                      size=20, trigger=0.30)
# engine.add_take_profit(market_id="my-market", side="yes", order_side="sell",
#                        size=20, trigger=0.60)
```

---

## 9. Backtesting a Strategy

Test on historical data before going live.

```python
import horizon as hz

def fair_value(ctx: hz.Context) -> float:
    return ctx.feed.price

def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    return hz.quotes(fair=fair, spread=0.04, size=10)

result = hz.backtest(
    name="my_backtest",
    markets=["market-1"],
    data=[
        {"timestamp": 1000, "price": 0.45},
        {"timestamp": 2000, "price": 0.48},
        {"timestamp": 3000, "price": 0.52},
        {"timestamp": 4000, "price": 0.55},
        {"timestamp": 5000, "price": 0.50},
    ],
    pipeline=[fair_value, quoter],
    risk=hz.Risk(max_position=100),
    outcomes={"market-1": 1.0},  # Market resolved YES
)

print(result.summary())
# Access metrics:
# result.sharpe, result.max_drawdown, result.win_rate, result.profit_factor
# result.to_csv("results.csv")
```

---

## 10. Using Pipeline Function from Built-in Modules

Compose with pre-built pipeline functions.

```python
import horizon as hz

hz.run(
    name="full_pipeline",
    markets=["my-market"],
    feeds={"default": hz.PolymarketBook("my-market")},
    pipeline=[
        hz.feed_guard(max_stale_secs=30),       # Pull quotes if feed is stale
        hz.volatility(window=20),                 # Compute rolling volatility
        hz.kelly_sizer(fraction=0.25, bankroll=1000),  # Kelly position sizing
        hz.market_maker(spread=0.04),             # Generate MM quotes
        hz.inventory_skewer(gamma=0.1),           # Skew for inventory
        hz.adaptive_spread(min_spread=0.02, max_spread=0.10),  # Dynamic spread
    ],
    risk=hz.Risk(max_position=100, max_notional=1000, max_drawdown_pct=5),
)
```

---

## 11. Live Trading with Polymarket

Switch from paper to live.

```python
import horizon as hz

def fair_value(ctx: hz.Context) -> float:
    return ctx.feed.price

def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    return hz.quotes(fair=fair, spread=0.04, size=5)

hz.run(
    name="live_mm",
    exchange=hz.Polymarket(),  # Reads credentials from env vars
    markets=["will-btc-hit-100k"],
    feeds={"default": hz.PolymarketBook("will-btc-hit-100k")},
    pipeline=[fair_value, quoter],
    risk=hz.Risk(max_position=50, max_notional=500, max_drawdown_pct=3),
    mode="live",
    dashboard=True,
)
```

Required env vars: `POLYMARKET_PRIVATE_KEY`, `POLYMARKET_API_KEY`, `POLYMARKET_API_SECRET`, `POLYMARKET_API_PASSPHRASE`

---

## 12. Logit-Space Market Making

Use logit-space pricing for correct behavior near 0 and 1.

```python
import horizon as hz

def fair_value(ctx: hz.Context) -> float:
    return ctx.feed.price

def logit_quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    inventory = ctx.inventory.net_for_market(ctx.market_id)

    # Logit-space reservation price (correct near boundaries)
    bid_price = hz.logit_reservation_price(
        mid=fair, inventory=inventory, gamma=0.5,
        belief_vol=0.2, t=1.0,
    )

    # Logit-space optimal spread
    spread = hz.logit_optimal_spread(
        belief_vol=0.2, inventory=inventory, gamma=0.5,
        kappa=1.5, t=1.0,
    )

    bid = max(0.01, bid_price - spread / 2)
    ask = min(0.99, bid_price + spread / 2)
    if bid >= ask:
        return []
    return [hz.Quote(bid=bid, ask=ask, size=5)]

hz.run(
    name="logit_mm",
    markets=["my-market"],
    feeds={"default": hz.PolymarketBook("my-market")},
    pipeline=[fair_value, logit_quoter],
    risk=hz.Risk(max_position=50, max_notional=500),
)
```

---

## 13. Cross-Exchange Arbitrage

Monitor price discrepancies between exchanges.

```python
import horizon as hz

# Use the built-in arb scanner pipeline
hz.run(
    name="cross_arb",
    exchanges=[hz.Polymarket(), hz.Kalshi()],
    markets=["btc-event-poly", "btc-event-kalshi"],
    feeds={
        "btc-event-poly": hz.PolymarketBook("btc-event-poly"),
        "btc-event-kalshi": hz.KalshiBook("KXBTC-25FEB16"),
    },
    pipeline=[hz.arb_scanner(threshold=0.03)],
    risk=hz.Risk(max_position=50, max_notional=500),
    netting_pairs=[("btc-event-poly", "btc-event-kalshi")],
    mode="live",
)
```

---

## 14. Using Custom Parameters (Hot Reload)

Pass runtime params that you can change without restarting.

```python
import horizon as hz

def fair_value(ctx: hz.Context) -> float:
    return ctx.feed.price

def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    spread = ctx.params.get("spread", 0.04)
    size = ctx.params.get("size", 10)
    return hz.quotes(fair=fair, spread=spread, size=size)

hz.run(
    name="configurable_mm",
    markets=["my-market"],
    feeds={"default": hz.PolymarketBook("my-market")},
    pipeline=[fair_value, quoter],
    risk=hz.Risk(max_position=100, max_notional=1000),
    params={"spread": 0.04, "size": 10},
)

# While running, update params via MCP or:
# hz.write_params({"spread": 0.06, "size": 5})
```

---

## 15. Kalshi Trading

```python
import horizon as hz

def fair_value(ctx: hz.Context) -> float:
    return ctx.feed.price

def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    return hz.quotes(fair=fair, spread=0.04, size=5)

hz.run(
    name="kalshi_mm",
    exchange=hz.Kalshi(),  # Reads KALSHI_EMAIL, KALSHI_PASSWORD from env
    markets=["KXBTC-25FEB16"],
    feeds={"default": hz.KalshiBook("KXBTC-25FEB16")},
    pipeline=[fair_value, quoter],
    risk=hz.Risk(max_position=50, max_notional=500),
    mode="live",
)
```
