Documentation Index
Fetch the complete documentation index at: https://mathematicalcompany.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
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:
"""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:
- Divides
duration_secs by num_slices to get the interval (6 seconds above).
- On each
on_tick() call, checks if enough time has passed for the next slice.
- Submits a child order of
total_size / num_slices contracts.
- 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:
"""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:
# 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:
"""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:
- Places a limit order for
show_size contracts.
- On each
on_tick(), checks whether the visible order is still open.
- When the visible order fills, calculates remaining size and places a new visible slice.
- 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:
"""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
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)