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:
This strategy is a tutorial strategy, learning-oriented, please use it with caution in real bot.