Live Options Market Making Quoter (OMM Pt.3)
Building a real-time volatility curve pricing model
Introduction
Note: The full Rust implementation of the quoter can be found in the appendix
So we’ve looked a lot at theoretical volatility curves, but this hasn’t yet been translated into something we can derive quotes from in real time. So today, we will write out a full pricing server. The server will spit out our delta neutral quotes. This is the first half of building a proper options market making system. It’s by far the easier part in terms of developer work, but does involve a lot of research and code still.
My model for developing HFT systems has always been that you have your pricing server and your trader server. Sometimes the pricing server is simply there to stream data and doesn’t do anything other than that, but often you use it to calculate fair values or in the case of taker strategies, different opportunities. Today, we will be building the first half of that - building a full options market maker is a very large lift and probably unrealistic for a single article. That said, this will still be a fairly large amount of code and a longer article as a result. Our system prices options and comes up with it’s own fair values without simply copying the market. If spot prices change, we have our own new option prices before the market updates. If a trade happens, we can price it into our curve as well. Probably nowhere near as good as other MMs, but that can be tuned in production.
We will assume that we have an empty portfolio for our quotes so there will be no skewing involved, but in a real system we would skew our quotes based on various risk factors (Greeks, notional limits, etc).
The hardest part of building an options market making system is the portfolio management, OEMS, risk management, reporting, controllers, data robustness handlers. There’s a world of difference between spitting out quotes in a system that doesn't have any sort of complex data quality checking logic + connection assurances + low latency optimizations for the data feeds, and what we will be doing. That said, the code below is by no means trivial and will involve us putting together components from all the prior articles as well as including many new additions.
Here is what some of the quotes look like when running live. You can see some of the fit parameters to the model, and then also the log-moneyness vs. the implied volatility for each of the strikes on the expiry:
If we were to plot the values it would look similar to the curve we fit in the previous article on option market making:
The only difference is that this is being done extremely fast and live. We use the same Python code under the hood as this uses SciPy’s minimize which is made of super fast FORTRAN anyways so the optimization time is still fairly acceptable. We also don’t need to fit too aggressively.
Further Work
As I mentioned earlier, this is not a full option market maker — merely the quoting component, we would need to implement execution to bring it live.
It also likely makes sense to incorporate VCR, SCR, SSR, etc into the model by using spot price updates to modify the curve in-between fits. I’ll show you how to do that live in the next article. The current implementation has the functions to do it implemented, but the spot feed has not been fit to yet.
From here, we would add on our skews our view on where the curve is going based on various fits and skews for different Greek preferences and we would have a half decent start at being an options market maker.
Now that we have this live code, I will use this as a platform in the next articles to show how this will be done in a live environment. I’ll also put down a quick dashboard so we can visualize it.
If you want to have a toy with it, the code is below.
Appendix
Here is the code for the quoter, to start here is the file directory overview:
options_pricing
├── python
│ └── wing_model.py
└── src
├── exchanges
│ ├── deribit.rs
│ └── mod.rs
├── volatility
│ └── wing.rs
├── main.rs
├── model.rs
├── py_utils.rs
├── quoter.rs
├── ticker_universe.rs
└── wing_model.rs
Cargo.toml
.gitignoreIt should look like this when it connects to the exchanges:
And here are the fitted surfaces which happen live:
main.rs:
// Optional: Remove this, only so it doesn't crowd my terminal.
#![allow(unused_variables, unreachable_code, unused_imports, deprecated, dead_code, unused_mut)]
pub mod ticker_universe;
pub mod model;
pub mod quoter;
pub mod exchanges;
pub mod py_utils;
pub mod wing_model;
pub mod volatility {
pub mod wing;
}
pub use exchanges::{
ExchangeClient,
DeribitClient,
};
use color_eyre::eyre::Error;
use model::Exchange;
use quoter::Quoter;
use ticker_universe::TickerUniverse;
#[tokio::main]
async fn main() -> Result<(), Error> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.init();
let client: DeribitClient = DeribitClient::connect_mainnet().await?;
let instruments = client.get_instruments(None, "option", Some(true), None).await?;
let universe = TickerUniverse::new(instruments, Exchange::Deribit);
let mut quoter = Quoter::new(Exchange::Deribit, universe, client);
quoter.start_quoting().await.expect("Error");
loop {};
Ok(())
}




