Stock Exchange

Market Maker

Automated market making and liquidity provision

What is a Market Maker?

A market maker is an automated agent that continuously provides liquidity to the market by placing both buy and sell orders. This ensures there's always someone to trade with and helps maintain healthy bid-ask spreads.

Market makers are essential for liquid, efficient markets. They profit from the bid-ask spread while providing a valuable service to traders.

How It Works

Basic Strategy

The market maker follows a simple but effective strategy:

Track Current Price

Monitor recent trades to determine the current market price.

Place Bid Orders

Place buy orders below the current price:

Bid Price = Current Price - Spread/2

Place Ask Orders

Place sell orders above the current price:

Ask Price = Current Price + Spread/2

Adjust Continuously

As the price moves or orders fill, continuously adjust the quotes to maintain presence on both sides.

Configuration

The market maker can be configured with several parameters:

type MarketMaker struct {
    UserID          int64
    TargetSpread    float64   // Desired bid-ask spread
    OrderSize       int       // Size of each order
    NumLevels       int       // Number of price levels
    RefreshInterval time.Duration
}

Example Configuration

mm := MarketMaker{
    UserID:          8,
    TargetSpread:    20.0,    // $20 spread
    OrderSize:       50,       // 50 shares per order
    NumLevels:       3,        // 3 levels on each side
    RefreshInterval: 5 * time.Second,
}

This creates an orderbook like:

Asks:
  1030  50 shares  (Current + 30)
  1020  50 shares  (Current + 20)
  1010  50 shares  (Current + 10)

Current Price: 1000

Bids:
  990   50 shares  (Current - 10)
  980   50 shares  (Current - 20)
  970   50 shares  (Current - 30)

Strategies

Simple Market Making

The basic strategy places orders at fixed intervals around the current price:

func (mm *MarketMaker) Quote(currentPrice float64) {
    halfSpread := mm.TargetSpread / 2
    
    // Place bid
    bidPrice := currentPrice - halfSpread
    mm.PlaceBidOrder(bidPrice, mm.OrderSize)
    
    // Place ask
    askPrice := currentPrice + halfSpread
    mm.PlaceAskOrder(askPrice, mm.OrderSize)
}

Multi-Level Market Making

Advanced market makers place orders at multiple price levels to provide depth:

for level := 1; level <= mm.NumLevels; level++ {
    offset := float64(level) * mm.PriceIncrement
    
    // Bid side
    bidPrice := currentPrice - offset
    mm.PlaceBidOrder(bidPrice, mm.OrderSize)
    
    // Ask side  
    askPrice := currentPrice + offset
    mm.PlaceAskOrder(askPrice, mm.OrderSize)
}

Inventory Management

Market makers must manage inventory to avoid excessive risk:

Position Limits

Set maximum long and short positions:

const (
    MaxLongPosition  = 1000  // Maximum shares to own
    MaxShortPosition = 1000  // Maximum shares to owe
)

Skewing Quotes

When inventory gets too high, adjust quotes to encourage trading in the desired direction:

if mm.Position > TargetPosition {
    // Too long - make selling more attractive
    bidPrice -= SkewAmount
    askPrice -= SkewAmount
} else if mm.Position < TargetPosition {
    // Too short - make buying more attractive
    bidPrice += SkewAmount
    askPrice += SkewAmount
}

Risk Management

Market makers face several risks:

Adverse Selection

Getting picked off when the market moves against you.

Mitigation:

  • Fast price updates
  • Wider spreads during volatility
  • Position limits

Inventory Risk

Holding too much or too little inventory.

Mitigation:

  • Dynamic position management
  • Skewing quotes based on inventory
  • Hedging with correlated instruments

Operational Risk

System failures, network issues, or bugs.

Mitigation:

  • Robust error handling
  • Position monitoring
  • Kill switches

Profitability

Market makers profit from:

  1. Bid-Ask Spread: Earn the spread on every round trip
  2. Rebates: Many exchanges offer maker rebates
  3. Price Improvement: Occasionally buy low and sell high

Example P&L

Buy:  100 shares @ 995  = -99,500
Sell: 100 shares @ 1005 = +100,500
                         ─────────
Profit:                  = 1,000 (1% on capital)

Implementation

Here's a simplified market maker implementation:

func (mm *MarketMaker) Run(ctx context.Context) {
    ticker := time.NewTicker(mm.RefreshInterval)
    defer ticker.Stop()
    
    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            mm.refresh()
        }
    }
}

func (mm *MarketMaker) refresh() {
    // Get current price
    currentPrice := mm.getCurrentPrice()
    
    // Cancel old orders
    mm.cancelAllOrders()
    
    // Place new orders
    for level := 1; level <= mm.NumLevels; level++ {
        offset := float64(level) * 10.0
        
        mm.PlaceBidOrder(currentPrice - offset, mm.OrderSize)
        mm.PlaceAskOrder(currentPrice + offset, mm.OrderSize)
    }
}

Monitoring

Key metrics to monitor:

  • Fill Rate: Percentage of orders that get filled
  • Spread Capture: Average profit per trade
  • Position: Current inventory position
  • PnL: Cumulative profit and loss
  • Quote Time: Time spent with active quotes

Advanced Topics

Adaptive Spreads

Adjust spreads based on:

  • Market volatility
  • Order flow toxicity
  • Competition from other market makers

Cross-Asset Market Making

Make markets in multiple correlated instruments and hedge across them.

High-Frequency Market Making

Optimize for ultra-low latency with:

  • Co-location
  • FPGA acceleration
  • Custom network protocols

Next Steps

On this page