1/ There's a lot of edge in building a production crypto HFT system the right way.
The abstractions should be rich enough to prevent careless mistakes but not too rigid to work with.
I converged on these after countless hours of building, debugging, and rewriting code 🧵
2/ Let's focus on a concrete example: How do you deal with continuous quantities?
Doing math in code with raw numeric types is error prone. Multiplying the price of one symbol by the size of another is sure way to blow up your strategy.
Just be careful? Nah, we can do better.
3/ A pervasive feature of our trading system is the concept of "units."
The first idea is to have separate types for Price, Size, Notional, etc.
But Size * Size? Nonsensical, and now you get a compiler error.
4/ This is already safer, but these quantities still all wrap raw floats.
We can do even better: It doesn't make sense to add 1 BTC to 2 ETH.
So sizes should be Size(2, ETH), and you can check that the units agree in each of your arithmetic implementations.
5/ To continue this type extension, prices would be Price(2000, USDT/BTC). That is, the price of one BTC is in units of USDT *per BTC*.
The units work out like:
Price(2000, USDT/BTC) * Size(2, BTC) = Notional(4000, USDT).
6/ To sanity check the progress so far, we can still do sensical computations like computing total exchange volume. You add a bunch of Price * Size = Notional(..., USDT) quantities.
However, we're still not done. Assets across exchanges are not fungible.
7/ When shit hits the fan, networks can be congested, or deposits and withdrawals can be closed by the exchange.
To ensure robustness, it's helpful to include the exchange as part of the symbol too, so that you can't directly compare BTC on Binance with BTC on Coinbase.
8/ Some seemingly benign things like computing the global "mid price" across BTC/USDT listed on all exchanges or setting USD=USDT are now more annoying.
But this is a feature, not a bug. We should think twice about these dangerous assumptions that are true almost always.
9/ There is a lot more to do here, for example fitting derivatives into this system. But this should be enough to get the idea.
The beauty of building a system from scratch is the freedom to rethink simple things. Use it wisely to help with the trading you're trying to do.
10/ Implementation-wise #rustlang makes it super ergonomic to create complex unit systems.
Define macros for type definitions, relationships, and constructors.
You can even do things like compile out the symbol checks in release mode, but check them in unit tests.
11/ By the way dimensional analysis is a super common tool in physics. When you do a big computation, the first sanity check on the answer is to check the units are correct.
This is just a way to do that automatically in your trading code.
12/ We've even extended these principles to Hyperliquid. Many concepts from HFT carry over naturally to building robust and performant exchanges.
Often principles transcend the specific application. When building complex systems, it's always worth setting strong foundations.
• • •
Missing some Tweet in this thread? You can try to
force a refresh
1/ We've talked before about how latency is an important component of every crypto HFT strategy.
The optimization process is fun detective work because the exchange's tech is often a black box.
A thread on what to measure, graph, and analyze for your production HFT system 🧵
2/ The first important number to look at is the "exchange timestamp" field of websocket messages and REST responses.
If multiple time fields exist, use the one corresponding to matching engine time. The other fields are edge server times, which add more noise to your analysis
3/ Matching engine time is crucial because network delay and the exchange's internal machine topology add noise to the outgoing timestamps on your end.
Reconcile the matching engine and your outgoing timestamps and you can quantify and optimize the factors in between