How To Correctly Reason About A Market Maker
Introduction
A market maker quotes bid and ask prices, earns the spread when both sides fill, and manages the inventory risk in between.
Everyone that writes an article on market making will hit you with the Avellaneda-Stoikov model and there will be a ton of calculus that comes attached with it. There is certainly no shortage of such articles, so I want to instead reason with you about the parameters of a market maker and why you should care about them rather than showing you a bunch of math.
What I’m more interested in doing is to give you a working quoting system that decides how wide to quote, how to skew quotes based on inventory, and when to stop quoting entirely that you can try as a reasonable first pass in any exchange of your choice.
A Short Plug
I’d like to take some time to answer a frequent question I get: “How does your content differ from the myriad quant content out there?“
Well, it’s simple and boils down to two simple things:
I actually eat my own cooking. I’ve managed money at scale and quite successfully. I just so happen to be in my leaking alpha for clout capture era. I don’t write about stuff I’ve never tried or thought deeply about. This means I do not flood you with BS.
My articles are going to be practical, to the point, and hopefully contains one insight that will make it useful. Most content are vacuous and do not contain any insights, therefore, they pad the content with “proofs”, “formulas” and “equations”. There is no need for that here. You will get the meat without the bones.
Today’s article, like many others before it, is free and educational, and serves as a foundation for more complex and interesting articles after this. I would greatly appreciate if you would share it with someone you think will benefit from this.
Even better, subscribe so you don’t miss all our learnings and discoveries on all things market makers!
The Three Components Of A Market Maker
A Base Spread
Your goal as a market maker is to make money, right?
You are going to make money essentially from the spread, and the spread is essentially the difference between your attempt at buying low (your bids) and selling high (your asks).
All your risks, basis risks, latency risks, adverse selection risks, and your cost of business needs to be reflected in the spread. If your spread is too small relative to your risks and your business costs, even if you trade a lot, you can’t sustain your business, and you go under.
If you spread is too wide, you are not able to trade enough to make decent profits off of quoting. For example, if your spread is $0.05, and you trade two round trips a day, you’d have made $0.10. This is clearly problematic!
So, the name of the game IS to adaptively size your spread such that you earn enough to make YOUR time worthwhile, given all the risks that you are bearing. It’s your cost of doing business.
This is where a model like Avellaneda-Stoikov would get you to have a spread that looks like:
def base_half_spread(gamma: float, k: float) -> float:
"""
gamma = inventory risk aversion (typically 0.01 to 0.5)
k = arrival decay (typically 0.5 to 3.0)
sigma = volatility
tau = time remaining (1.0 for continuous market making)
"""
return (1 / gamma) * np.log(1 + gamma / k) + 0.5 * gamma * sigma**2 * tauThe arrival decay parameter k controls how sensitive fill probability is to distance from mid-price. Remember the part where we said that if your spread is too wide, you wouldn’t trade enough? Basically k controls this. You want to find a “k” that allows you to “trade enough”.
High k means fill probability drops sharply as you move away, so you need to quote tighter to get fills. Low k means fills are less distance-sensitive, so you can quote wider without losing much flow.
Gamma measures your aversion to inventory risk specifically, not trading risk. This means that higher gamma leads to tighter spreads because when you are more averse to holding inventory, you want to turn it over faster. Faster turnover means more frequent trading. More frequent trading means tighter spreads. You give up some edge per trade, but you spend less time exposed to inventory risk.
There is a volatility-dependent component (sigma) that compensates you for inventory risk over your holding period. This is actually important because it means the spread should widen when volatility increases, because your risks of prices moving away from you also increases.
The tau parameter represents time horizon. Tau allows you to have a lever that controls how “impatient” you are to let go of your positions.
Inventory Skew
Your goal is to have round-trip trades to earn the spread right? Which means you want to get rid of your inventory when you are holding inventory. To do this, you basically allow yourself to “nudge” your quotes in the opposite direction that you are holding inventory (for example, if you are long an instrument, you want to nudge your quotes to sell cheaper).
def reservation_price(
mid: float,
inventory: int,
gamma: float,
sigma: float,
tau: float = 1.0,
) -> float:
"""
Calculate the reservation price for a market maker.
mid = current mid-price
inventory = position (positive = long, negative = short)
gamma = risk aversion parameter
sigma = volatility
tau = time remaining (1.0 for continuous market making)
"""
return mid - inventory * gamma * sigma**2 * tauThe term “reservation price” comes from microeconomics - it is the price at which you are indifferent to trading. When you hold inventory, your personal fair value differs from mid because you bear the risk that prices move against you.
The skew penalizes inventory proportional to the variance of price change over your holding period. If volatility doubles, your inventory risk quadruples, so the skew quadruples.
When you are long (inventory > 0), your reservation price drops below mid. Both quotes shift down, making your ask more attractive to buyers and your bid less attractive to sellers. Net effect: you are more likely to sell than buy, pushing inventory back toward zero.
When you are short, the opposite happens - quotes shift up, encouraging buys over sells. The effect is a mean-reverting force on your inventory. Skew is proportional to position size: the bigger your inventory deviation from zero, the harder the quotes push you back.
The tau parameter represents time horizon. Tau allows you to have a lever that controls how “impatient” you are to let go of your positions.
Limits, Limits, Limits
No matter how well designed your market maker, you are surely going to run into cases where you have hit your risk limits in terms of inventory and/or PnL limits.
In that case, you just want to STOP QUOTING. Often times, you have inventory limits where, once you hit it, you will stop quoting on the side of the inventory limits.
On the PnL side, once you hit your PnL limits, you will simply quickly exit out of your inventory and stop quoting for a brief moment of time.
A Simplified Market Maker
from dataclasses import dataclass
from typing import Optional, Tuple
import math
@dataclass
class MarketMakerParams:
sigma: float # Volatility
gamma: float # Risk aversion
k: float # Arrival decay
Q: int # Position limit
def compute_quotes(
mid: float,
inventory: int,
params: MarketMakerParams,
tick_size: float = 0.01,
) -> Tuple[Optional[float], Optional[float]]:
quote_bid = inventory < params.Q
quote_ask = inventory > -params.Q
half_spread = (1 / params.gamma) * np.log(1 + params.gamma / params.k)
reservation = mid - inventory * params.gamma * params.sigma**2
bid = round((reservation - half_spread) / tick_size) * tick_size if quote_bid else None
ask = round((reservation + half_spread) / tick_size) * tick_size if quote_ask else None
return bid, ask
params = MarketMakerParams(sigma=0.02, gamma=0.1, k=1.5, Q=100)
bid, ask = compute_quotes(mid=50.00, inventory=10, params=params)Calibrating Your Parameters
You need to estimate three parameters from market data. The fourth (risk aversion) is a choice.
Volatility (sigma): Rolling standard deviation of log returns - that is ln(P_t / P_{t-1}) rather than simple percentage changes. Use 20-60 minute windows intraday.
Arrival decay (k): Requires limit order book (LOB) data. Track fills at different distances from mid over several days, compute fill rates per distance, take logs, regress log fill rate against distance - the slope is k. Typical values: 0.5 (illiquid) to 3.0 (liquid).
Without LOB data, start with k = 1.5 and adjust based on realized fill rates.
Risk aversion (gamma): Start with 0.1. If inventory swings are too wide, increase it. If you are hitting position limits too often, decrease it. Pick gamma that keeps inventory within desired bounds under normal conditions.
Improving Your Simple Market Maker
If you have a view on where price is going, you can incorporate it in two ways: shifting your reservation price or skewing your spread. The simpler approach is to replace mid with mid + alpha, where alpha is your expected price change over your quoting horizon. A positive alpha shifts both quotes up, making your bid more competitive and your ask less so, accumulating long inventory in anticipation of the move.
But rather than moving both quotes in the same direction, you can instead skew the spread itself: tighten on the side you want filled, widen on the side you don’t. If you’re bullish, you want to get hit on your bid and don’t want to get lifted on your ask, so you multiply your bid-side spread by (1 - skew) and your ask-side spread by (1 + skew), where skew is alpha normalized by your base half-spread.
Reservation shifts change where you quote. Spread skews change how aggressively you quote on each side while keeping quotes centered around a neutral mid.
You can combine both approaches, but many practitioners find spread skew alone is cleaner because it directly maps your forecast into asymmetric fill probabilities without taking an implicit position before you’ve been filled.
Either way, the key constraint is that your forecast should exceed your spread costs to be worth acting on. And once you incorporate forecasts, you’re no longer a pure market maker. You’re a directional trader using market making as execution, which means higher PnL variance and sharper consequences for being wrong.
Limitations and Edge Cases
Adverse selection. Market makers get fisted by informed order flow. If you are getting picked off by informed traders, realized spread will be negative. Track realized spread (execution price minus mid at fill) over thousands of trades and add the deficit to your base half-spread.
Market impact. The model assumes your quotes do not move mid-price. If your order size exceeds 5-10% of top-of-book depth, you need market impact models. Most retail and small institutional market makers never hit this threshold.
Real-life Market Frictions. In reality, you are in a queue. Updating your quotes loses your place. Hence, most market making algorithms have some kind of hysteresis built in.
Conclusion
You now have a complete market making quoting system. You are probably going to get your ass handed to you in highly competitive markets like BTCUSDT in Binance. But, in an illiquid market or a soft one, like Polymarket, even a simple market maker like this should suffice.

