This time, the strategy brought by FMZ Quant is Dynamic Delta Hedging of Deribit Options, abbreviated as DDH.
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:
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
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:
Strategy address: https://www.fmz.com/strategy/265090
The strategy is a tutorial, mainly used for study, so please be careful to use in a bot.