Deribit Options Delta Dynamic Hedging Strategy
Tags: strategy, data, fmzquant, hedging
Published: 2024-05-06 02:14:30

The strategy that FMZ Quant brings to the table is the Deribit Options Delta Dynamic Hedging strategy, or DDH (dynamic delta hedging) strategy for short.

### For the study of options trading, we usually have to master these concepts:

· Option pricing model, B-S model, option prices are determined based on [subject matter price], [power-wield price], [remaining time to expiration], [(implied) volatility], and [risk-free rate].

· Exposure of options:

-Delta -- The directional risk of an option. If the Delta is +0.50, then the option, which performs profit or loss when the subject matter price goes up or down, can be considered as 0.50 spot.
-Gamma -- Acceleration of directional risk. For example, in the case of call options, due to the Gamma, the Delta will gradually move from +0.50 to +1.00 as the price continues to rise from the time the subject matter price is at the power-wield price.
-Theta -- Time Exposure. When you buy an option, if the subject matter price does not change, for each day that passes, you pay the fee shown in the Theta amount (Deribit is denominated in USD).
  When you sell an option, if the subject matter price does not change, you will receive the fee shown in the Theta amount every day.
-Vega -- Volatility Exposure. When you buy an option, Vega is positive, i.e. going long volatility. When the implied volatility increases, you gain on the Vega exposure. And vice versa, when you sell options, you gain as implied volatility decreases.

### DDH strategy explanation:
· DDH principle explanation
Risk-neutral trading direction is achieved by matching the Delta of options and futures. Since the option's Delta follows the change of the subject matter price, the Delta of futures and spots is unchanged.
After taking a position in an option contract and balancing the Delta with futures hedging, the overall Delta will become unbalanced again as the subject matter price moves. Such a combination of options and futures positions requires continuous dynamic hedging to equalize Delta.

· For example:
When we buy a call option, we hold a position in the long direction at this point. At this time, it is necessary to go short the futures to hedge the Delta of options, reaching the overall Delta neutral (0 or close to 0).
Let's ignore the remaining time to expiration of the option contract, volatility and other factors.
Scenario 1:
When the price of the subject matter rises, the Delta of the options increases, and the overall Delta moves to a positive number, and futures need to hedge again. Open some short positions to continue to go short the futures, so that the overall Delta is balanced again.
(Before re-balancing, the Delta of options is large, while that of futures is relatively small. The marginal profit of call options exceeds the marginal loss of contract short positions, and the entire portfolio will yield.)

Scenario 2:
As the price of the subject matter falls, the option portion of the Delta decreases and the overall Delta moves to a negative number, closing out a portion of the short futures position and bringing the overall Delta into balance again.
(Before re-balancing, at this time, the Delta of options is small, while that of futures is relatively large. The marginal loss of call options is less than the marginal profit of contract short positions, and the entire portfolio will still yield gains.)

Therefore, in an ideal state, the increase and decrease of the subject matter will bring benefits as long as the market fluctuates.

However, there are also factors to consider: time value, transaction costs, and other factors.

Therefore, the explanation of hotshot on Zhihu is quoted:
```
The focus of Gamma Scalping is not on delta, dynamic delta hedging is just a way to avoid underlying price risk in the process.
Gamma Scaling focuses on alpha, which is not the alpha of stock selection. Here, alpha=gamma/theta, that is, how much gamma is exchanged for the time loss of unit Theta.
This is the point of concern. It is possible to construct a portfolio that floats both up and down, but it must be accompanied by time loss, and then the problem lies in the cost effectiveness.

Author: Xu Zhe
URL: https://www.zhihu.com/question/51630805/answer/128096385
```

### DDH strategy design explanation
· Aggregate market interface encapsulation, framework design
· Strategy UI design
· Strategic interaction design
· Automatic hedge function design
Source code:
```
// Construct functions
function createManager(e, subscribeList, msg) {
	var self = {}
    self.supportList = ["Futures_Binance", "Huobi", "Futures_Deribit"]  // of the supported exchanges

    // Object attributes
    self.e = e
    self.msg = msg
    self.name = e.GetName()
    self.type = self.name.includes("Futures_") ? "Futures" : "Spot"
    self.label = e.GetLabel()
    self.quoteCurrency = ""  
    self.subscribeList = subscribeList   // subscribeList : [strSymbol1, strSymbol2, ...]
    self.tickers = []                    // All market data obtained by the interface, define the data format: {bid1: 123, ask1: 123, symbol: "xxx"}}
    self.subscribeTickers = []           // The required market data, define the data format: {bid1: 123, ask1: 123, symbol: "xxx"}}
    self.accData = null 
    self.pos = null 

    // Initialize the function
    self.init = function() { 
    	// Judge if the exchange is supported
        if (!_.contains(self.supportList, self.name)) {        	
        	throw "not support"
        }
    }

    self.setBase = function(base) {
        // Switching base address for switching to analog bot
        self.e.SetBase(base)
        Log(self.name, self.label, "switch to analog bot:", base)
    }

    // Judging data precision
    self.judgePrecision = function (p) {
        var arr = p.toString().split(".")
        if (arr.length != 2) {
            if (arr.length == 1) {
                return 0
            }
            throw "judgePrecision error, p:" + String(p)
        }
        
        return arr[1].length
    }

    // Update assets
    self.updateAcc = function(callBackFuncGetAcc) {
        var ret = callBackFuncGetAcc(self)
        if (!ret) {
        	return false 
        }
        self.accData = ret 
        return true 
    }

    // Update positions
    self.updatePos = function(httpMethod, url, params) {
        var pos = self.e.IO("api", httpMethod, url, params)
        var ret = []
        if (!pos) {
            return false 
        } else {
            // Organize data
            // {"jsonrpc":"2.0","result":[],"usIn":1616484238870404,"usOut":1616484238870970,"usDiff":566,"testnet":true}
            try {
                _.each(pos.result, function(ele) {
                    ret.push(ele)
                })
            } catch(err) {
                Log("Error:", err)
                return false 
            }
            self.pos = ret
        }
        return true 
    }

    // Update the market data
    self.updateTicker = function(url, callBackFuncGetArr, callBackFuncGetTicker) {
    	var tickers = []
    	var subscribeTickers = []
    	var ret = self.httpQuery(url)
    	if (!ret) {
    		return false 
    	}
    	// Log("test", ret)// test
    	try {
            _.each(callBackFuncGetArr(ret), function(ele) {
            	var ticker = callBackFuncGetTicker(ele)
            	tickers.push(ticker)
                if (self.subscribeList.length == 0) {
                    subscribeTickers.push(ticker)
                } else {
                	for (var i = 0 ; i < self.subscribeList.length ; i++) {                        
                    	if (self.subscribeList[i] == ticker.symbol) {
                    		subscribeTickers.push(ticker)
                    	}
                	}
                }
            })
        } catch(err) {
        	Log("Error:", err)
        	return false 
        }

        self.tickers = tickers
        self.subscribeTickers = subscribeTickers
        return true 
    }

    self.getTicker = function(symbol) {
    	var ret = null 
    	_.each(self.subscribeTickers, function(ticker) {
    		if (ticker.symbol == symbol) {
    			ret = ticker
    		}
    	})
    	return ret 
    }

    self.httpQuery = function(url) {
    	var ret = null
        try {
            var retHttpQuery = HttpQuery(url)
            ret = JSON.parse(retHttpQuery)
        } catch (err) {
            // Log("Error:", err)
            ret = null
        }
        return ret 
    }

    self.returnTickersTbl = function() {
        var tickersTbl = {
        	type : "table", 
        	title : "tickers",
        	cols : ["symbol", "ask1", "bid1"], 
        	rows : []
        }
        _.each(self.subscribeTickers, function(ticker) {        
        	tickersTbl.rows.push([ticker.symbol, ticker.ask1, ticker.bid1])
        })
        return tickersTbl
    }
    
    // Back to the position table
    self.returnPosTbl = function() {
        var posTbl = {
            type : "table", 
            title : "pos|" + self.msg,
            cols : ["instrument_name", "mark_price", "direction", "size", "delta", "index_price", "average_price", "settlement_price", "average_price_usd", "total_profit_loss"], 
            rows : []
        }
        /* Format of the position data returned by the interface
        {
            "mark_price":0.1401105,"maintenance_margin":0,"instrument_name":"BTC-25JUN21-28000-P","direction":"buy",
            "vega":5.66031,"total_profit_loss":0.01226105,"size":0.1,"realized_profit_loss":0,"delta":-0.01166,"kind":"option",
            "initial_margin":0,"index_price":54151.77,"floating_profit_loss_usd":664,"floating_profit_loss":0.000035976,
            "average_price_usd":947.22,"average_price":0.0175,"theta":-7.39514,"settlement_price":0.13975074,"open_orders_margin":0,"gamma":0
        }
        */
        _.each(self.pos, function(ele) {
        	if(ele.direction != "zero") {
                posTbl.rows.push([ele.instrument_name, ele.mark_price, ele.direction, ele.size, ele.delta, ele.index_price, ele.average_price, ele.settlement_price, ele.average_price_usd, ele.total_profit_loss])
            }
        })
        return posTbl
    }

    self.returnOptionTickersTbls = function() {
        var arr = []
        var arrDeliveryDate = []
        _.each(self.subscribeTickers, function(ticker) {
            if (self.name == "Futures_Deribit") {
                var arrInstrument_name = ticker.symbol.split("-")
                var currency = arrInstrument_name[0]
                var deliveryDate = arrInstrument_name[1]
                var deliveryPrice = arrInstrument_name[2]
                var optionType = arrInstrument_name[3]

                if (!_.contains(arrDeliveryDate, deliveryDate)) {
                    arr.push({
                        type : "table", 
                        title : arrInstrument_name[1],
                        cols : ["PUT symbol", "ask1", "bid1", "mark_price", "underlying_price", "CALL symbol", "ask1", "bid1", "mark_price", "underlying_price"], 
                        rows : []
                    })
                    arrDeliveryDate.push(arrInstrument_name[1])
                }
                // Iterate through arr
                _.each(arr, function(tbl) {
                    if (tbl.title == deliveryDate) {
                        if (tbl.rows.length == 0 && optionType == "P") {
                            tbl.rows.push([ticker.symbol, ticker.ask1, ticker.bid1, ticker.mark_price, ticker.underlying_price, "", "", "", "", ""])
                            return 
                        } else if (tbl.rows.length == 0 && optionType == "C") {
                            tbl.rows.push(["", "", "", "", "", ticker.symbol, ticker.ask1, ticker.bid1, ticker.mark_price, ticker.underlying_price])
                            return 
                        }                        
                        for (var i = 0 ; i < tbl.rows.length ; i++) {
                            if (tbl.rows[i][0] == "" && optionType == "P") {
                                tbl.rows[i][0] = ticker.symbol
                                tbl.rows[i][1] = ticker.ask1
                                tbl.rows[i][2] = ticker.bid1
                                tbl.rows[i][3] = ticker.mark_price
                                tbl.rows[i][4] = ticker.underlying_price
                                return 
                            } else if(tbl.rows[i][5] == "" && optionType == "C") {
                                tbl.rows[i][5] = ticker.symbol
                                tbl.rows[i][6] = ticker.ask1
                                tbl.rows[i][7] = ticker.bid1
                                tbl.rows[i][8] = ticker.mark_price
                                tbl.rows[i][9] = ticker.underlying_price
                                return 
                            }
                        }
                        if (optionType == "P") {
                            tbl.rows.push([ticker.symbol, ticker.ask1, ticker.bid1, ticker.mark_price, ticker.underlying_price, "", "", "", "", ""])
                        } else if(optionType == "C") {
                            tbl.rows.push(["", "", "", "", "", ticker.symbol, ticker.ask1, ticker.bid1, ticker.mark_price, ticker.underlying_price])
                        }
                    }
                })
            }
        })
        return arr 
    }

    // Initialization
    self.init()
	return self 
}


function main() {
    // Initialization, clear logs
    if(isResetLog) {
    	LogReset(1)
    }

    var m1 = createManager(exchanges[0], [], "option")
    var m2 = createManager(exchanges[1], ["BTC-PERPETUAL"], "future")

    // Switch to analog bot
    var base = "https://www.deribit.com"
    if (isTestNet) {    
        m1.setBase(testNetBase)    
        m2.setBase(testNetBase)
        base = testNetBase
    }

    while(true) {
        // Options
        var ticker1GetSucc = m1.updateTicker(base + "/api/v2/public/get_book_summary_by_currency?currency=BTC&kind=option", 
            function(data) {return data.result}, 
            function(ele) {return {bid1: ele.bid_price, ask1: ele.ask_price, symbol: ele.instrument_name, underlying_price: ele.underlying_price, mark_price: ele.mark_price}}) 
        
        // Perpetual futures
        var ticker2GetSucc = m2.updateTicker(base + "/api/v2/public/get_book_summary_by_currency?currency=BTC&kind=future", 
            function(data) {return data.result}, 
            function(ele) {return {bid1: ele.bid_price, ask1: ele.ask_price, symbol: ele.instrument_name}})
        if (!ticker1GetSucc || !ticker2GetSucc) {
            Sleep(5000)
            continue
        }

        // Update positions
        var pos1GetSucc = m1.updatePos("GET", "/api/v2/private/get_positions", "currency=BTC&kind=option")
        var pos2GetSucc = m2.updatePos("GET", "/api/v2/private/get_positions", "currency=BTC&kind=future")

        if (!pos1GetSucc || !pos2GetSucc) {
            Sleep(5000)
            continue
        }

        // Interactions
        var cmd = GetCommand()
        if(cmd) {
            // Handle interactions
            Log("Interaction commands", cmd)
            var arr = cmd.split(":")
            // cmdClearLog 
            if(arr[0] == "setContractType") {
                // parseFloat(arr[1])
                m1.e.SetContractType(arr[1])
                Log("exchanges[0] contract set by exchange object.", arr[1])
            } else if (arr[0] == "buyOption") {
                var actionData = arr[1].split(",")
                var price = parseFloat(actionData[0])
                var amount = parseFloat(actionData[1])
                m1.e.SetDirection("buy")
                m1.e.Buy(price, amount)
                Log("execution price: ", price, "execution amount: ", amount, "execution direction: ", arr[0])
            } else if (arr[0] == "sellOption") {
                var actionData = arr[1].split(",")
                var price = parseFloat(actionData[0])
                var amount = parseFloat(actionData[1])
                m1.e.SetDirection("sell")
                m1.e.Sell(price, amount)                
                Log("execution price: ", price, "execution amount: ", amount, "execution direction: ", arr[0])
            } else if (arr[0] == "setHedgeDeltaStep") {
                hedgeDeltaStep = parseFloat(arr[1])
                Log("set the parameter hedgeDeltaStep:", hedgeDeltaStep)
            } 
        }
        
        // Obtain the future contract prices
        var perpetualTicker = m2.getTicker("BTC-PERPETUAL")
        var hedgeMsg = " PERPETUAL:" + JSON.stringify(perpetualTicker)

        // Obtain the total delta value from the account data        
        var acc1GetSucc = m1.updateAcc(function(self) {
        	self.e.SetCurrency("BTC_USD")        
        	return self.e.GetAccount()
        })
        if (!acc1GetSucc) {
        	Sleep(5000)
        	continue
        }
        var sumDelta = m1.accData.Info.result.delta_total

        if (Math.abs(sumDelta) > hedgeDeltaStep && perpetualTicker) {
            if (sumDelta < 0) {
                // Hedging futures go short if delta is greater than 0                 
                var amount = _N(Math.abs(sumDelta) * perpetualTicker.ask1, -1)                
                if (amount > 10) {
                    Log("Exceed the hedging threshold, current total delta:", sumDelta, "Buy futures")
                    m2.e.SetContractType("BTC-PERPETUAL")                    
                    m2.e.SetDirection("buy")
                    m2.e.Buy(-1, amount)
                } else {
                	hedgeMsg += ", hedging order volume less than 10"
                }
            } else {
                // Hedging futures go long if delta is less than 0
                var amount = _N(Math.abs(sumDelta) * perpetualTicker.bid1, -1)
                if (amount > 10) {
                    Log("Exceed the hedging threshold, current total delta:", sumDelta, "Sell futures")
                    m2.e.SetContractType("BTC-PERPETUAL")
                    m2.e.SetDirection("sell")
                    m2.e.Sell(-1, amount)
                } else {
                	hedgeMsg += ", hedging order volume less than 10"
                }
            }
        }

        LogStatus(_D(), "sumDelta:", sumDelta, hedgeMsg, 
        	"\n`" + JSON.stringify([m1.returnPosTbl(), m2.returnPosTbl()]) + "`", "\n`" + JSON.stringify(m2.returnTickersTbl()) + "`", "\n`" + JSON.stringify(m1.returnOptionTickersTbls()) + "`")
        Sleep(10000)
    }
}
```

Strategy Address: https://www.fmz.com/strategy/265090

Strategy operation:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mjrjj6abrsvye16pa6pw.png)

This strategy is a tutorial strategy, learning-oriented, please use it with caution in real bot.

From: https://blog.mathquant.com/2022/11/01/deribit-options-delta-dynamic-hedging-strategy.html