Skip to main content

Overview

The GLFT model (Guéant, Lehalle & Fernandez-Tapia, 2013) solves the optimal market-making problem: how to set bid and ask prices that maximize expected profit while controlling inventory risk. The key insight is that optimal quotes depend on three factors:
  1. Volatility of the underlying asset
  2. Your current inventory (position risk)
  3. Order arrival rate (how often you get filled)

The Model

The optimal bid and ask offsets from the fair price are:
reservation_price = mid - q * gamma * sigma^2 * T

optimal_spread = gamma * sigma^2 * T + (2/gamma) * ln(1 + gamma/k)

bid = reservation_price - optimal_spread / 2
ask = reservation_price + optimal_spread / 2
Where:
  • mid = current fair price (midpoint)
  • q = current inventory (positive = long, negative = short)
  • gamma = risk aversion parameter (higher = wider spreads, less inventory risk)
  • sigma = volatility of the underlying asset
  • T = time remaining until market close
  • k = order arrival decay parameter (how quickly fill probability drops with quote distance from mid)
These are the closed-form approximations from Avellaneda & Stoikov (2008). The full GLFT solution (Guéant, Lehalle & Fernandez-Tapia, 2013) uses a finite-horizon ODE with matrix exponential.

Implementation

import math
import time
from py_blink_client import ClobClient, BlinkPriceWs, OrderArgs, Side

client = ClobClient(
    host="https://api.blink15.com",
    key="0xYOUR_KEY",
)
client.create_or_derive_api_creds()

# --- GLFT Parameters ---
GAMMA = 0.1          # risk aversion (tune this)
K = 1.5              # order arrival intensity (tune this)
SIZE = 50            # contracts per side
REFRESH_SEC = 3      # re-quote interval


def estimate_volatility(price_history: list[float]) -> float:
    """Estimate annualized volatility from recent price ticks."""
    if len(price_history) < 10:
        return 0.01  # default low volatility
    returns = [
        math.log(price_history[i] / price_history[i - 1])
        for i in range(1, len(price_history))
        if price_history[i - 1] > 0
    ]
    if not returns:
        return 0.01
    variance = sum(r * r for r in returns) / len(returns)
    return math.sqrt(variance)


def glft_quotes(mid: float, inventory: int, sigma: float, time_remaining: float) -> tuple[float, float]:
    """Compute GLFT optimal bid and ask."""
    T = max(time_remaining / 900, 0.001)  # normalize to [0, 1] over 15-min window

    # Reservation price: skew away from inventory
    reservation = mid - inventory * GAMMA * sigma**2 * T

    # Optimal spread
    spread = GAMMA * sigma**2 * T + (2 / GAMMA) * math.log(1 + GAMMA / K)

    bid = round(reservation - spread / 2, 2)
    ask = round(reservation + spread / 2, 2)

    # Clamp to valid range
    bid = max(0.01, min(0.99, bid))
    ask = max(0.01, min(0.99, ask))

    return bid, ask


# --- Price tracking ---
price_history = []

def on_price_tick(msg):
    price_history.append(float(msg['price']))
    if len(price_history) > 500:
        price_history.pop(0)

price_ws = BlinkPriceWs("https://api.blink15.com")
price_ws.on_price_tick = on_price_tick
price_ws.start()
price_ws.subscribe(["BTCUSD"])

# --- Main loop ---
markets = client.get_markets()
market = next(m for m in markets if m.status == "Active")

inventory = 0  # track from fills

time.sleep(5)  # collect some price ticks

while True:
    mid = client.get_midpoint(market.yes_token_id)
    mid_price = float(mid["mid"])

    sigma = estimate_volatility(price_history)
    time_remaining = (market.close_time - time.time())

    if time_remaining <= 0:
        print("Market closed, stopping")
        break

    bid, ask = glft_quotes(mid_price, inventory, sigma, time_remaining)

    client.cancel_all()
    client.create_and_post_orders([
        OrderArgs(token_id=market.yes_token_id, side=Side.BUY, price=bid, size=SIZE),
        OrderArgs(token_id=market.yes_token_id, side=Side.SELL, price=ask, size=SIZE),
    ])

    print(f"GLFT: {bid} / {ask} (mid={mid_price}, inv={inventory}, vol={sigma:.4f}, T={time_remaining:.0f}s)")
    time.sleep(REFRESH_SEC)

Parameter Tuning

Risk Aversion (gamma)

GammaBehaviorBest For
0.01Tight spreads, high fill rate, more inventory riskHigh-volume, low-volatility markets
0.1Balanced spreadsGeneral purpose
1.0Wide spreads, low fill rate, minimal inventory riskHigh-volatility or uncertain markets

Order Arrival Rate (k)

Estimate from historical trade frequency. Higher k means tighter spreads are optimal (you’ll get filled more often).
# Estimate k from recent trades
trades = client.get_user_trades("0xYourAddress")
recent = [t for t in trades if t.timestamp > time.time() - 3600]
k = len(recent) / 3600  # trades per second

Volatility (sigma)

Use Pyth oracle ticks for real-time volatility estimation:
# Realized volatility from last N ticks
sigma = estimate_volatility(price_history[-100:])
For prediction markets, the relevant volatility is in the token price, not just the underlying asset. Token prices near 0.50 are most volatile; near 0.01 or 0.99, they barely move.

Market Windows

Markets have a defined close time. As T approaches 0:
  • Spread widens (higher uncertainty per unit time)
  • Inventory skew increases (more aggressive position reduction)
This is correct behavior — you want to be flat when the market settles.

Binary Outcome

Unlike continuous assets, prediction market tokens converge to 0 or 1 at expiry. The GLFT model’s spread naturally accounts for this via the T term — as expiry approaches, the model quotes wider to reflect settlement risk.

MINT/MERGE Liquidity

Blink supports cross-book matching via MINT and MERGE:
  • A BUY on YES + BUY on NO at combined price $1.00 creates new tokens (MINT)
  • A SELL on YES + SELL on NO burns tokens and returns $1.00 (MERGE)
This means liquidity on one side implicitly creates liquidity on the other. Your quotes on YES affect NO book depth and vice versa.

References

  • Guéant, O., Lehalle, C.A., & Fernandez-Tapia, J. (2013). “Dealing with the Inventory Risk: A Solution to the Market Making Problem.” Mathematics and Financial Economics, 7(4), 477-507.
  • Guéant, O. (2017). “Optimal Market Making.” Applied Mathematical Finance, 24(2), 112-154.