nkaz001 / hftbacktest

A high-frequency trading and market-making backtesting tool in Python and Rust, which accounts for limit orders, queue positions, and latencies, utilizing full tick data for trades and order books, with real-world crypto market-making examples for Binance Futures
MIT License
1.75k stars 347 forks source link

Implementation remarks #21

Closed sandstorm111 closed 1 year ago

sandstorm111 commented 1 year ago

Dear nkaz,

First of all, I'd like to thank you very much for this amazing repository. I feels good to find someone who`s trying to implement the same papers that I've been reading over the last few years and also trying to implement myself.

I still haven`t been able to successfully implement those papers in production, and I am really (really) looking forward to it. I would to leave some comments, maybe we can find a solution together.

First remark, on the effect of kappa on the bid and ask quotes. On "Dealing with the Inventory Risk", section 6.5, it is discussed that kappa impacts the bid-ask spread. However, as pointed out by Lehalle on stack exchange, there are two reference papers that describe how to calculate k and A. They are Tapia's thesis and Sophie Laruelle's "Faisabilité de l’apprentissage des paramètres d’un algorithme de trading sur des données réelles". Even though it's in French, it is possible to see on section 3.5 that she describes kappas and As for bid and ask separately.

Suppose bid kappa is very high, and ask kappa is very low. This means that the bid will be offset to a much lower price, while the ask will be offset towards the mid price. The concept of half spread doesn't exist anymore, in the sense that the spread is not symmetric even if the inventory is 0. From my interpretation, it's an implicit adjustment to the market conditions, meaning that the demand for bids is higher than the demand for asks (market is likely in a downtrend). The market maker is more likely to be losing money is such conditions, if he assumes equal kappas for both sides.

The seconds remark is regarding the multi asset solution utilizing Gueant's framework. You are skipping the part where you have to compute the correlation matrix of the asset's in your portfolio, so your "inventory" doesn't have to be an inventory in terms of quantities (even though it sounds a good idea to hardcode raw inventory limits) but in terms of delta in respect to your reference asset.

Suppose a market making portfolio of ETHUSDT and BTCUSDT, and the correlation between ETHBTC is 0.8. If you are long 80 BTC, and short 100 ETH, your delta is basically 0 and you can continue quoting without necessarily adjusting your quotes to zero your inventories, as discussed in item 6 of Gueant's "Optimal Market Making".

From my personal experience, the hardest part of making money with theses models is in regards to being forced to send adjusted quotes when the market is against you (you may very well lose your profits and realize a loss) and the second difficulty is in regards to the velocity at which you accumulate inventory that goes against you. You have hardcoded adj2 to circumvent that, but still will be losing excl fees.

My user is sandstorm111#2242 on discord, if you feel like contacting me directly.

Kind regards

sandstorm111 commented 1 year ago

Sorry if I have sounded too harsh in the last part of my comment and by no means I want to demerit your work, I just wanted to point out that the skew may be not necessary if delta is close to zero.

nkaz001 commented 1 year ago

Thank you for your thorough feedback.

Primarily, this repository is designed to provide a framework for high-frequency trading backtesting, rather than a trading strategy.

The examples serve as demonstrations, showing the concept of market making as one of the types of high-frequency trading for which this backtesting tool is required.

Even though examples are examples, I believe they can work in practice if you qualify as a market maker and can receive the highest rebates. This conclusion is based on my live trading tests that show results very close to backtest results when fees are paid.

Nonetheless, I agree with your observations.

I have found that trading intensity yields better results when measured for both bid and ask. Separating the measurements seems more rational, but it seems to require more sophisticated measurements.

Regarding skew, as you also mentioned, hedging the portfolio(skewing based on portfolio inventory risk) is a more effective way to manage inventory risk. I believe the simplest first step is to use the dollar-term inventory of the portfolio to skew towards market neutrality. And then, the next step would be to utilize correlation, beta, cointegration and etc. I attempted to backtest this method, but the current Numba implementation has limitations, making it challenging to implement multi-asset backtesting. Therefore, I am in the process of rewriting the code in Rust.

As for the last part, the severe drawdown in a fast-moving market is observable in the backtest as well. When I artificially reduced latency, the drawdown was minimized. Crypto exchanges not only provide rebates but also offer low-latency APIs to market makers. So, depending on the latency of these low-latency APIs, they could react even in fast markets. Additionally, hedging the market-making portfolio can help address this issue.

I'm not sure if you are a qualified market maker who receives rebates, but for me, the first step to implementing this in practice is to find a strategy that achieves a high transaction volume while keeping costs as low as possible in order to become a market maker.

Under the highest rebates, it seems feasible to achieve a very high transaction volume even if the strategy is losing, as the loss is compensated by the rebates. This scenario basically equates to purchasing a market maker license. However, the challenge lies in reaching that level initially while managing an affordable cost. This might not be possible for retail traders, but it's worth considering nonetheless.

By the way, have you tried the xi = 0 case? It looks a better option, as it's simpler and it eliminates the need to introduce adjustment factors. I plan to update the example soon.

sandstorm111 commented 1 year ago

Indeed, I see the rebate point. I'll give the best of my creativity to find a plan to get to VIP 6 or at least 5 on Binance. It's a bit of a bummer though, because even in an exchange like Deribit, where limit orders on the perpetuals are free, it's hard to make money. The weekly settled instruments don't have too much volume.

Regarding the technological part, even affordable colocation, it's fairly doable to me in almost all regards. The hardest part is to find a profitable model. Currently, I don't make any market and it's the part where I'm stuck at.

Ironically, what I found interesting about your project, is that I am in the same level of backtest in a project that I have in C++, except in regards of dynamic latency. This project uses L3 data from a major futures exchange (non-crypto) and I'm able to reproduce the top of book; the key difference lies in the latency simulation class design. the order entry can only communicate to the matching engine via the latency class, and vice versa. The latency class is designed to hold and release the model's order in a is slightly different way than the "elapse and goto" pattern. The queue model doesn't exist, because the queue can be perfectly reproduced. So reading your project and models almost feels like reading something that I could have written.

Haven't tried the xi=0 case, I currently have a bigger problem (my business model) to solve haha. Theoretically, I should make sense to have to xi=lambda for coins that are not very liquid (since epsilon measures the counterparty risk). In practice, as you said, that may not be the case. An idea that incurred to me is to set xi=[a variable that is proportional to the liquidity pool of the coin in respect to the total liquidity pool of the portfolio], in a way that the xi of large cap coins tend to 0, and the xi of small caps tend to lambda. This could be hardcoded as well, though less optimal for scalability.

sandstorm111 commented 1 year ago

If you have a formal training in math, could you please help me out on this issue? I'm pretty sure that the proposition 4 on dealing with the inventory risk is wrong: https://quant.stackexchange.com/questions/74530/dealing-with-the-inventory-risk-solution-with-drift/75778#75778

nkaz001 commented 1 year ago

I have also observed that it may be more difficult to make a profit in other crypto exchanges. This could potentially be due to differences in market microstructure. Since Binance provides rebates only to eligible market makers, opportunities remain for these market makers who are capable of receiving rebates. If fees are zero or if rebates are offered to every participant posting limit orders, the market microstructure naturally changes, meaning opportunities may be lost. In comparison to Binance Futures, the transaction volumes of other crypto exchanges are generally very low, presenting fewer opportunities in terms of uninformed trades.

In addition, short-term alpha eventually plays an important role, as implied by the model with drift and as described in other literature.

You might be interested in the following article as an example of short-term alpha.

https://www.ma.imperial.ac.uk/~ajacquie/Gatheral60/Slides/Gatheral60%20-%20Stoikov.pdf https://www.youtube.com/watch?v=S7eig5VXFpY

I plan to implement L3 backtesting once I've finished rewriting the code in Rust. I believe it will be interesting for us to discuss this when the time comes.

richwomanbtc commented 1 year ago

If you have a formal training in math, could you please help me out on this issue? I'm pretty sure that the proposition 4 on dealing with the inventory risk is wrong: https://quant.stackexchange.com/questions/74530/dealing-with-the-inventory-risk-solution-with-drift/75778#75778

For prop.4, the boundary condition is $q=-Q,$ so it is correct that $-\beta (-Q) = +\beta Q$. On the other hand, the prop 4 matrix is probably wrong in the paper: from the definition of v in prop.2, the upper left element corresponds to the aforementioned $q=-Q$ boundary condition, so the diagonal should align $\alpha q^2 - \beta q (q=-Q, . . . , Q)$. With the boundary condition with $v(T-0) = v(T)$, the solution of the linear differential equation expressed by $d\vec{v}/dt = A\vec{v}$ is $v(t) = e^{-A(T-t)}v(T)$. If $A$ can be diagonalized to $A=PDP^{-1}$, then $v(t) = Pe^{-(T-t)D}P^{- 1}v(T)$, which means that only the eigenvector with the smallest eigenvalue remains at $T\rightarrow\infty$.

richwomanbtc commented 1 year ago

As for the optimal limit price, one can immediately see that the drift term, i.e. $-\frac{\beta}{\alpha}$, is added to the result below prop 3. without actually calculating it via the eigenvectors. So I think it's just that they are not aware if the matrix is wrong.

sandstorm111 commented 1 year ago

@richwomanbtc thank you very much for your explanation!

I made a repository with the implementation, the solution with drift is implement with both +beta and -beta. I'm not normally an open-source committer, but I think it is the minimum I can do to give back.

https://github.com/sandstorm111/solution_to_the_inventory_risk

@nkaz001 and @richwomanbtc, kind regards :)