Skip to main content
Every pipeline function receives a Context object with live data about feeds, inventory, the current market, engine status, and custom parameters.

Context Structure

@dataclass
class Context:
    feeds: dict[str, FeedData]      # Live feed snapshots
    inventory: InventorySnapshot     # Current positions
    market: Market | None            # Current market being quoted
    event: Event | None              # Current event (multi-outcome only)
    status: EngineStatus | None      # Engine status (PnL, kill switch, etc.)
    params: dict[str, Any]           # Custom parameters from hz.run(params=...)

FeedData

Each entry in ctx.feeds is a FeedData snapshot from the engine’s feed manager:
@dataclass
class FeedData:
    price: float = 0.0        # Last trade price
    timestamp: float = 0.0    # Unix timestamp
    bid: float = 0.0          # Best bid
    ask: float = 0.0          # Best ask

Accessing feeds

def fair_value(ctx: hz.Context) -> float:
    btc = ctx.feeds.get("btc", hz.context.FeedData())
    return btc.price  # Latest price from feed
Always use .get() with a default FeedData() to handle the case where a feed hasn’t received data yet.

Feed staleness

FeedData includes a staleness check:
feed = ctx.feeds.get("btc", hz.context.FeedData())
if feed.is_stale(max_age_secs=30.0):
    # Feed hasn't updated in 30 seconds, consider skipping quotes
    pass
In live mode, the strategy loop automatically skips quoting for markets when any feed is stale (default threshold: 30 seconds). You can configure this via params:
hz.run(
    params={"feed_stale_threshold": 60.0},  # Allow up to 60s staleness
    ...
)

InventorySnapshot

ctx.inventory provides the current position state:
@dataclass
class InventorySnapshot:
    positions: list[Position]   # All open positions

    @property
    def net(self) -> float:
        """Net position across all markets (YES - NO)."""

    def net_for_market(self, market_id: str) -> float:
        """Net position for a specific market (YES - NO)."""

    def net_for_event(self, market_ids: list[str]) -> float:
        """Net position across all markets in an event."""

    def positions_for_event(self, market_ids: list[str]) -> list[Position]:
        """Filter positions for markets in an event."""

Inventory-based quoting

def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    # Skew quotes based on inventory
    net_pos = ctx.inventory.net
    skew = net_pos * 0.001
    return hz.quotes(fair - skew, spread=0.04, size=5)

Per-market inventory

def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    market_pos = ctx.inventory.net_for_market(ctx.market.id)
    # Reduce size when position is large
    size = max(1, 10 - abs(market_pos) * 0.1)
    return hz.quotes(fair, spread=0.04, size=size)

Market

ctx.market is the Market object currently being quoted:
def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    if ctx.market.exchange == "kalshi":
        spread = 0.04  # Wider spread for Kalshi
    else:
        spread = 0.02
    return hz.quotes(fair, spread, size=5)
See Types Reference for all Market fields.

Event

ctx.event is set when trading multi-outcome events (via hz.run(events=...)). It is None for standard binary market strategies.
def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    if ctx.event:
        # Access event info
        event_market_ids = [o.market_id for o in ctx.event.outcomes]
        event_pos = ctx.inventory.net_for_event(event_market_ids)
        skew = event_pos * 0.001
        return hz.quotes(fair - skew, spread=0.04, size=5)
    return hz.quotes(fair, spread=0.04, size=5)
See Multi-Outcome Events for the full guide.

EngineStatus

ctx.status provides engine-level metrics:
def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    if ctx.status.kill_switch_active:
        return []  # Don't quote when kill switch is on

    # Reduce size as drawdown increases
    drawdown = -ctx.status.daily_pnl
    size = max(1, 10 - drawdown * 0.5)
    return hz.quotes(fair, spread=0.04, size=size)

Custom Parameters

Pass arbitrary parameters via hz.run(params=...) and access them in pipeline functions:
hz.run(
    params={
        "gamma": 0.1,
        "base_spread": 0.02,
        "max_inventory": 50,
    },
    ...
)

def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
    gamma = ctx.params["gamma"]
    spread = ctx.params["base_spread"]
    max_inv = ctx.params["max_inventory"]

    # Widen spread as inventory approaches limit
    inv_ratio = abs(ctx.inventory.net) / max_inv
    adjusted_spread = spread * (1 + gamma * inv_ratio)

    return hz.quotes(fair, adjusted_spread, size=5)