---
title: "Cross-Exchange Arb"
description: "Atomic cross-exchange arbitrage with auto-rollback, pipeline scanner, and one-shot sweep."
icon: "arrows-split-up-and-left"
---

<Note>
  **Pro Feature.** Requires a Pro or Ultra subscription. [Get started at api.mathematicalcompany.com](https://api.mathematicalcompany.com)
</Note>

# Cross-Exchange Arbitrage

Buy on one exchange where the price is low, sell on another where it's high. Horizon's executor handles both legs atomically with auto-rollback on failure.

## How It Works

When the same market is priced differently across exchanges (e.g., Polymarket bid > Kalshi ask), you can buy on the cheap exchange and sell on the expensive one for risk-free profit.

```
Kalshi ask      = $0.48  (buy here)
Polymarket bid  = $0.52  (sell here)
Raw edge        = $0.04
Net edge        = $0.04 - fees
```

## Engine Methods

### scan_arbitrage

```python
opps = engine.scan_arbitrage(
    "election-winner",
    [("polymarket", "polymarket", 0.002), ("kalshi", "kalshi", 0.002)],
    max_liquidity=100.0,
)
```

The tuple format is `(feed_name, exchange_name, fee_rate)`.

### execute_arbitrage

```python
buy_id, sell_id = engine.execute_arbitrage(
    market_id="election-winner",
    buy_exchange="kalshi",
    sell_exchange="polymarket",
    buy_price=0.48,
    sell_price=0.52,
    size=10.0,
)
```

**Behavior:**
1. Both legs go through the full risk pipeline
2. Orders use Fill-Or-Kill (FOK) time-in-force
3. If the buy succeeds but the sell fails, the buy is automatically canceled
4. Returns `(buy_order_id, sell_order_id)` on success

## Pipeline: arb_scanner

```python
scanner = hz.arb_scanner(
    market_id="election-winner",
    exchanges=["polymarket", "kalshi"],
    feed_map={"polymarket": "polymarket", "kalshi": "kalshi"},
    min_edge=0.02,
    auto_execute=True,
    cooldown=10.0,
)

hz.run(pipeline=[scanner], ...)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `market_id` | `str` | required | Market to scan |
| `exchanges` | `list[str]` | required | Exchanges to compare |
| `feed_map` | `dict[str, str]` | required | Exchange name -> feed name |
| `min_edge` | `float` | `0.01` | Minimum net edge |
| `max_size` | `float` | `50.0` | Max execution size |
| `fee_rates` | `dict[str, float] or None` | `None` | Fee rates per exchange |
| `auto_execute` | `bool` | `False` | Auto-execute |
| `cooldown` | `float` | `5.0` | Seconds between executions |

Stores `ArbResult` in `ctx.params["last_arb"]` on execution.

## One-Shot: arb_sweep

```python
result = hz.arb_sweep(
    engine=engine,
    market_id="election-winner",
    feed_map={"polymarket": "polymarket", "kalshi": "kalshi"},
    min_edge=0.01,
)
```

Returns `ArbResult` or `None`.

## Example: Full Cross-Exchange Bot

```python
import horizon as hz

scanner = hz.arb_scanner(
    market_id="election-winner",
    exchanges=["polymarket", "kalshi"],
    feed_map={"polymarket": "polymarket", "kalshi": "kalshi"},
    min_edge=0.02,
    auto_execute=True,
    cooldown=10.0,
)

hz.run(
    name="cross_exchange_arb",
    exchanges=[
        hz.Polymarket(private_key="0x..."),
        hz.Kalshi(api_key="..."),
    ],
    markets=["election-winner"],
    feeds={
        "polymarket": hz.PolymarketBook("election-winner"),
        "kalshi": hz.KalshiBook("KXELECTION-25"),
    },
    pipeline=[scanner],
    interval=0.5,
)
```

<Warning>
  Cross-exchange arb requires active accounts on both exchanges with sufficient margin. The atomic rollback only cancels the buy leg. It cannot guarantee cancel success if the order was already filled.
</Warning>
