High-Frequency Trading and Latency Arbitrage › Market Making at High Frequency
Quantitative FinanceAll chapters High-Frequency Trading and Latency Arbitrage › Market Making at High FrequencyOne section · Sections in this chapter11 / 18 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Many HFT firms operate as electronic market makers, building on the principles we covered in the previous chapter but implementing them at much higher speeds and with more sophisticated risk management.
Quote Management
High-frequency market makers continuously update their quotes to reflect changing market conditions. A key challenge is managing the risk of being "picked off," which occurs when your stale quotes are executed against by faster traders who have already seen a price change.
The quote update process involves these steps:
- Receiving market data (prices, order flow, news)
- Updating fair value estimate
- Calculating new bid and ask quotes
- Sending cancel messages for old quotes
- Sending new quote messages
The entire cycle must complete faster than competitors can trade against your stale quotes.
def simulate_hft_market_maker(
n_periods: int = 1000,
true_price_vol: float = 0.10, # Price volatility per period (percentage)
spread_bps: float = 2.0,
inventory_limit: int = 1000,
fill_probability: float = 0.3,
adverse_selection_bps: float = 0.5,
):
"""
Simulate a high-frequency market maker.
Parameters:
-----------
n_periods : Number of time periods
true_price_vol : True price volatility per period (%)
spread_bps : Bid-ask spread in basis points
inventory_limit : Maximum inventory position
fill_probability : Probability of quote being filled each period
adverse_selection_bps : Loss per fill due to adverse selection
"""
np.random.seed(42)
# Initialize
inventory = 0
cash = 0
pnl_history = []
inventory_history = []
# Starting price
price = 100.0
for t in range(n_periods):
# True price evolution (random walk)
price_change = np.random.normal(0, true_price_vol)
price = price * (1 + price_change / 100)
# Market maker quotes around estimated fair value
# (slight lag means some adverse selection)
estimated_price = price * (
1 - np.random.normal(0, adverse_selection_bps / 10000)
)
half_spread = spread_bps / 10000 / 2 * estimated_price
bid = estimated_price - half_spread
ask = estimated_price + half_spread
# Adjust for inventory (lean against position)
# Inventory skewing formula: shift = (inventory / limit) * spread * factor
inventory_skew = (inventory / inventory_limit) * half_spread * 0.5
bid -= inventory_skew
ask -= inventory_skew
# Random fills
bid_fill = (
np.random.random() < fill_probability
and inventory < inventory_limit
)
ask_fill = (
np.random.random() < fill_probability
and inventory > -inventory_limit
)
# Process fills
if bid_fill:
inventory += 100
cash -= bid * 100
if ask_fill:
inventory -= 100
cash += ask * 100
# Mark to market
mtm_value = cash + inventory * price
pnl_history.append(mtm_value)
inventory_history.append(inventory)
pnl_history = np.array(pnl_history)
inventory_history = np.array(inventory_history)
# Calculate returns
returns = np.diff(pnl_history)
return {
"final_pnl": pnl_history[-1],
"pnl_history": pnl_history,
"inventory_history": inventory_history,
"sharpe": returns.mean() / returns.std() * np.sqrt(252 * n_periods)
if returns.std() > 0
else 0,
"max_drawdown": np.min(
pnl_history - np.maximum.accumulate(pnl_history)
),
"avg_inventory": np.abs(inventory_history).mean(),
}
mm_results = simulate_hft_market_maker()HFT Market Maker Simulation ======================================== Final P&L: $1,848.75 Sharpe Ratio: 15.19 Max Drawdown: $-1,114.19 Average Absolute Inventory: 552 shares
The simulation finishes with a profit, but its path is volatile rather than steadily increasing. Mark-to-market P&L peaks around the middle of the run and then gives back part of those gains. Inventory also spends extended periods at the 1,000-share limit before later crossing below zero. These outputs show that the current quote-skewing rule does not consistently maintain neutral inventory and that the final profit should be interpreted alongside substantial inventory and drawdown risk.