Dynamic Delta Hedging of Deribit Options

Author: Ninabadass, Created: 2022-04-24 11:32:48, Updated: 2022-04-24 15:50:56

Dynamic Delta Hedging of Deribit Options

This time, the strategy brought by FMZ Quant is Dynamic Delta Hedging of Deribit Options, abbreviated as DDH.

For the study of options trading, we usually need to master the concepts in several aspects:

  • Option pricing model; B-S model; option price is determined based on “underlying price”, “strike price”, “days to expiration”, “(implied) volatility” and “non-risk interest rate”.

  • Option risk exposures:

    • Delta — option directional risk. If the delta value is +0.5, the profit and loss performance of the option when the underlying price rises and falls can be regarded as 0.50 spot.
    • Gamma — the accelerated speed of directional risk. For example, a call option. Due to Gamma, from where the underlying price is at the strike price, Delta will get close to +1.00 from +0.50, in the increasing process of the price.
    • Theta — time decay. When you buy options, if the underlying price remains constant, with each passing day, you will pay a charge displayed by the Theta value (Deribit is priced in USD). When you sell options, and the underlying price remains constant, with each passing day, you will receive a fee displayed by Theta value.
    • Vega — volatility exposure. When you buy options, Vega is expressed as a positive value, namely long implied volatility. When the implied volatility increases, you can make profit by exposed to Vega. The opposite situation is also like that. When you sell options, the implied volatility decrease, and you will have profits.

DDH Strategy Explanation:

  • DDH Principle Explanation By balancing the delta of options and futures, the risk neutrality of the trading direction is achieved. Since the option delta changes as the underlying price changes, the delta of futures and spot will be unchanged. After holding an option contract position and using futures to hedge and balance the Delta, as the underlying price changes, the overall Delta will appear unbalanced again. For the combination of options positions and futures positions, constant dynamic hedging is required to balance Delta.

    For example: When we buy a call option, we have a bullish position. At this time, it is necessary to short futures to hedge the option Delta to achieve overall Delta neutrality (0 or close to 0). Let’s ignore the factors, such as days to expiration and the implied volatility of the option contract. Scenario 1: When the underlying price rises, the option Delta increases, and the overall Delta moves to a positive number. Futures is needed to hedge again, and some short positions are opened to continue to short futures, so that the overall Delta is balanced again. (Before re-balancing, the option delta is large, the futures delta is relatively small, the marginal profit of the call option exceeds the marginal loss of the short contract, and the entire portfolio will make a profit.) Scenario 2: When the underlying price falls, the option delta decreases, and the overall delta moves to a negative number, and some short futures positions are closed to make the overall delta balance again. (Before re-balancing, the option delta is small, the futures delta is relatively large, the marginal loss of the call option is less than the marginal profit of the short contract, and the entire portfolio will still have a profit.)

    Therefore, ideally, the rise and fall of the underlying both bring profits, as long as the market fluctuates.

    However, there are other factors that need to be considered: time value, trading costs and others.

    So, I quoted the explanation of a master from Zhihu:

    The focus of Gamma Scalping is not delta, dynamic delta hedging is just a way to avoid underlying price risk in the process. Gamma Scalping focuses on Alpha. The Alpha is not the Alpha of stock selection. Here, Alpha = Gamma/Theta, that is, how much Gamma is exchanged by the time decay of unit Theta. That is the point. It is possible to build a combination of rise and fall both with floating profits, surely accompanied with time decay, and the problem is the ratio of cost performance. Author: Xu Zhe; original article link: https://www.zhihu.com/question/51630805/answer/128096385

DDH Strategy Design

  • encapsulation of aggregated market interface, structure design;
  • strategy UI design;
  • strategy interaction design;
  • automatic hedging function design.

Source code:

// constructor 
function createManager(e, subscribeList, msg) {
	var self = {}
    self.supportList = ["Futures_Binance", "Huobi", "Futures_Deribit"]  // from the supported platforms

    // 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 as: {bid1: 123, ask1: 123, symbol: "xxx"}}
    self.subscribeTickers = []           // the market data in need; define the data format as: {bid1: 123, ask1: 123, symbol: "xxx"}}
    self.accData = null 
    self.pos = null 

    // initialization function 
    self.init = function() { 
    	// judge whether the platform is supported 
        if (!_.contains(self.supportList, self.name)) {        	
        	throw "not support"
        }
    }

    self.setBase = function(base) {
        // switch base address, used to switch to the simulated bot 
        self.e.SetBase(base)
        Log(self.name, self.label, "switch to simulated bot:", base)
    }

    // judge the 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 {
            // arrange 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
    }
    
    // return the positon 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 : []
        }
        /* the position data format 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])
                }
                // traverse 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 
    }

    // initialize 
    self.init()
	return self 
}


function main() {
    // initialize, and vacuum logs
    if(isResetLog) {
    	LogReset(1)
    }

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

    // switch to the simulated 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
        }

        // interaction 
        var cmd = GetCommand()
        if(cmd) {
            // process interaction 
            Log("interactive command:", cmd)
            var arr = cmd.split(":")
            // cmdClearLog 
            if(arr[0] == "setContractType") {
                // parseFloat(arr[1])
                m1.e.SetContractType(arr[1])
                Log("exchanges[0] sets contract:", 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("executed price:", price, "executed amount:", amount, "executed 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("executed price:", price, "executed amount:", amount, "executed direction:", arr[0])
            } else if (arr[0] == "setHedgeDeltaStep") {
                hedgeDeltaStep = parseFloat(arr[1])
                Log("set hedgeDeltaStep:", hedgeDeltaStep)
            } 
        }
        
        // obtain futures contract price 
        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) {
                // delta value is more than 0, hedge futures and make short                   
                var amount = _N(Math.abs(sumDelta) * perpetualTicker.ask1, -1)                
                if (amount > 10) {
                    Log("exceeding the hedging threshold value, the current total delta:", sumDelta, "call futures")
                    m2.e.SetContractType("BTC-PERPETUAL")                    
                    m2.e.SetDirection("buy")
                    m2.e.Buy(-1, amount)
                } else {
                	hedgeMsg += ", hedging order amount is less than 10"
                }
            } else {
                // delta value is less than 0, hedge futures and make long
                var amount = _N(Math.abs(sumDelta) * perpetualTicker.bid1, -1)
                if (amount > 10) {
                    Log("exceeding the hedging threshold value, the current total delta:", sumDelta, "put futures")
                    m2.e.SetContractType("BTC-PERPETUAL")
                    m2.e.SetDirection("sell")
                    m2.e.Sell(-1, amount)
                } else {
                	hedgeMsg += ", hedging order amount is less than 0"
                }
            }
        }

        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 parameters: img

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

Strategy Operation:

img

img

The strategy is a tutorial, mainly used for study, so please be careful to use in a bot.


More