---
title: "Horizon Execution Algorithms"
description: "Execute large orders with TWAP, VWAP, and Iceberg algorithms."
icon: "layer-group"
---

When you need to fill a large order without moving the market, Horizon provides three execution algorithms: **TWAP** (time-weighted), **VWAP** (volume-weighted), and **Iceberg** (hidden size). Each splits a parent order into smaller child orders managed by the engine.

## TWAP (Time-Weighted Average Price)

Splits a large order into equal-sized slices submitted at regular time intervals:

```python
"""TWAP execution: split a large order into time-weighted slices."""

from horizon import Engine, OrderRequest, Side, OrderSide, RiskConfig
from horizon import TWAP

engine = Engine(risk_config=RiskConfig(max_position_per_market=10000))

# Create a large parent order
request = OrderRequest(
    market_id="btc-100k",
    side=Side.Yes,
    order_side=OrderSide.Buy,
    size=100.0,
    price=0.55,
)

# TWAP: 100 contracts over 60 seconds in 10 slices
twap = TWAP(engine, duration_secs=60.0, num_slices=10)
twap.start(request)

# Each cycle, call on_tick with current price
twap.on_tick(current_price=0.55, timestamp=0.0)
print(f"TWAP complete: {twap.is_complete}")
print(f"Child orders: {len(twap.child_order_ids)}")
```

### How TWAP works

```
Time:   0s    6s    12s   18s   24s   30s   36s   42s   48s   54s
Slice:  10    10    10    10    10    10    10    10    10    10
        |     |     |     |     |     |     |     |     |     |
        v     v     v     v     v     v     v     v     v     v
        [=====|=====|=====|=====|=====|=====|=====|=====|=====|=====]
        0                          60s
        Total: 100 contracts in 10 equal slices
```

Each slice is submitted as an independent limit order at the current market price. The algorithm:

1. Divides `duration_secs` by `num_slices` to get the interval (6 seconds above).
2. On each `on_tick()` call, checks if enough time has passed for the next slice.
3. Submits a child order of `total_size / num_slices` contracts.
4. Tracks total filled across all children and marks complete when the target is reached.

### When to use TWAP

- **Low-urgency fills** where minimizing market impact matters more than speed.
- **Markets with thin books** where placing the full size at once would walk the book.
- **Predictable scheduling**: TWAP gives you even participation over the time window.

## VWAP (Volume-Weighted Average Price)

Slices the order proportionally to a volume profile, concentrating execution during high-volume periods:

```python
"""VWAP execution: volume-weighted slicing based on a historical profile."""

from horizon import Engine, OrderRequest, Side, OrderSide, RiskConfig
from horizon import VWAP

engine = Engine(risk_config=RiskConfig(max_position_per_market=10000))

request = OrderRequest(
    market_id="btc-100k",
    side=Side.Yes,
    order_side=OrderSide.Buy,
    size=100.0,
    price=0.55,
)

# Volume profile: more volume in first and last buckets (U-shaped)
volume_profile = [3.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 3.0]

# VWAP: 100 contracts over 60 seconds following the volume profile
vwap = VWAP(engine, duration_secs=60.0, volume_profile=volume_profile)
vwap.start(request)

# Drive the algo. In a real loop this runs each tick cycle
vwap.on_tick(current_price=0.55, timestamp=0.0)
print(f"VWAP complete: {vwap.is_complete}")
print(f"Child orders: {len(vwap.child_order_ids)}")
print(f"Total filled: {vwap.total_filled}")
```

### How VWAP works

The volume profile determines how much of the total order goes into each time slice:

```
Volume profile: [3, 1, 1, 1, 1, 1, 1, 1, 1, 3]
Total weight:   14
Slice sizes:    21.4, 7.1, 7.1, 7.1, 7.1, 7.1, 7.1, 7.1, 7.1, 21.4
                ^^^^                                              ^^^^
                Heavy at open and close (U-shaped pattern)
```

Each weight in the profile is normalized by the total weight, then multiplied by the target size. Slices are evenly spaced in time across `duration_secs`.

### When to use VWAP

- **Benchmarking against market VWAP**: your fills will match the volume pattern.
- **High-volume windows**: concentrate execution when liquidity is deepest.
- **Relative value strategies** where you want to match the market's natural flow.

### Building a volume profile

You can construct volume profiles from historical data or use common patterns:

```python
# U-shaped: heavy at open and close
u_shape = [3.0, 1.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.5, 3.0]

# Front-loaded: execute mostly at the start
front_loaded = [5.0, 3.0, 2.0, 1.0, 1.0, 0.5, 0.5, 0.5, 0.5, 0.5]

# Uniform (equivalent to TWAP)
uniform = [1.0] * 10

# From historical data
import csv
def load_volume_profile(csv_path: str) -> list[float]:
    """Load hourly volume buckets from a CSV."""
    volumes = []
    with open(csv_path) as f:
        reader = csv.DictReader(f)
        for row in reader:
            volumes.append(float(row["volume"]))
    return volumes
```

## Iceberg (Hidden Size)

Shows only a small visible portion of the total order. When the visible slice fills, a new one is placed:

```python
"""Iceberg: hide the true order size behind a small visible clip."""

from horizon import Engine, OrderRequest, Side, OrderSide, RiskConfig
from horizon import Iceberg

engine = Engine(risk_config=RiskConfig(max_position_per_market=10000))

request = OrderRequest(
    market_id="btc-100k",
    side=Side.Yes,
    order_side=OrderSide.Buy,
    size=100.0,   # True size: 100 contracts
    price=0.55,
)

# Iceberg: show only 10 contracts at a time
iceberg = Iceberg(engine, show_size=10.0)
iceberg.start(request)

# On each tick, the algo checks if the visible order has been filled
# and submits a new visible slice if needed
iceberg.on_tick(current_price=0.55, timestamp=0.0)
print(f"Iceberg complete: {iceberg.is_complete}")
print(f"Child orders placed: {len(iceberg.child_order_ids)}")
print(f"Total filled: {iceberg.total_filled}")
```

### How Iceberg works

```
Total order: 100 contracts
Show size:   10 contracts

Visible:   [10]  -> filled -> [10] -> filled -> [10] -> ... -> [10]
Hidden:    [===========90 remaining=============]    ...    [===0===]
                                                            Complete!
```

The algorithm:

1. Places a limit order for `show_size` contracts.
2. On each `on_tick()`, checks whether the visible order is still open.
3. When the visible order fills, calculates remaining size and places a new visible slice.
4. Repeats until the full target size is filled.

### When to use Iceberg

- **Avoid signaling**: other market participants cannot see your true order size.
- **Thin prediction markets** where showing 100 contracts would discourage counterparty flow.
- **Passive fills**: each slice rests at your limit price, earning the spread.

## Running an Algo in a Loop

All three algorithms follow the same `ExecAlgo` interface. Here is a complete example driving TWAP inside a loop:

```python
"""Complete TWAP execution loop with fill tracking."""

import time
from horizon import Engine, OrderRequest, Side, OrderSide, RiskConfig
from horizon import TWAP

engine = Engine(risk_config=RiskConfig(max_position_per_market=10000))

request = OrderRequest(
    market_id="btc-100k",
    side=Side.Yes,
    order_side=OrderSide.Buy,
    size=50.0,
    price=0.55,
)

twap = TWAP(engine, duration_secs=30.0, num_slices=5)
twap.start(request)

start_time = time.time()
while not twap.is_complete:
    elapsed = time.time() - start_time
    current_price = 0.55  # In production, fetch from feed

    # Tick the paper exchange (simulates matching)
    engine.tick("btc-100k", current_price)

    # Drive the algo
    twap.on_tick(current_price=current_price, timestamp=elapsed)

    print(
        f"  t={elapsed:.1f}s | "
        f"children={len(twap.child_order_ids)} | "
        f"filled={twap.total_filled:.0f}/{request.size:.0f}"
    )
    time.sleep(1.0)

print(f"\nDone! Total filled: {twap.total_filled}")
print(f"Child order IDs: {twap.child_order_ids}")
```

## Comparison

| Feature | TWAP | VWAP | Iceberg |
|---|---|---|---|
| **Slicing** | Equal time intervals | Volume-weighted intervals | On-fill replacement |
| **Market impact** | Low (spread out) | Lowest (follows liquidity) | Low (small visible) |
| **Speed** | Fixed duration | Fixed duration | Depends on fill rate |
| **Best for** | Low urgency, even execution | Matching market flow | Hiding order size |
| **Inputs** | `duration_secs`, `num_slices` | `duration_secs`, `volume_profile` | `show_size` |

## API Reference

All algorithms inherit from `ExecAlgo` and share these properties and methods:

| Member | Type | Description |
|---|---|---|
| `start(request)` | Method | Begin execution of a parent order. |
| `on_tick(current_price, timestamp)` | Method | Called each cycle to manage child orders. |
| `is_complete` | Property | `True` when the target size has been fully filled. |
| `child_order_ids` | Property | List of all child order IDs submitted so far. |
| `total_filled` | Property | Total contracts filled across all children. |

### Constructor signatures

```python
TWAP(engine, duration_secs=60.0, num_slices=10)
VWAP(engine, duration_secs=60.0, volume_profile=[1.0, 2.0, 3.0, ...])
Iceberg(engine, show_size=10.0)
```
