avatar of 发明者量化-小小梦 发明者量化-小小梦
집중하다 사신
4
집중하다
1271
수행원

보류 주문 선물 헤지 전략 설계에 관한 연구 및 사례

만든 날짜: 2021-11-26 14:15:31, 업데이트 날짜: 2023-09-20 09:24:03
comments   3
hits   2149

보류 주문 선물 헤지 전략 설계에 관한 연구 및 사례

보류 주문 선물 헤지 전략 설계에 관한 연구 및 사례

선물 및 현물 헤징은 항상 가격 차이를 감지하고 가격 차이가 발생하면 헤징 주문을 받도록 설계되었습니다. 이것을 보류 주문 헤지로 설계할 수 있나요? 대답은 ‘예’입니다. 오늘은 독자 여러분께 보류 주문 헤지에 대한 디자인 아이디어와 코드 프로토타입을 소개하겠습니다.

보류 중인 주문 헤지 아이디어

서로 다른 시장에서 같은 기초 자산이나 동일한 유형의 기초 자산에 대한 매수 주문과 매도 주문에 큰 차이가 있는 경우, 헤지 거래의 기회가 있습니다. 일반적으로 우리는 가격 차이를 충족하는 시장 주문을 받고 헤지 포지션을 유지합니다. 따라서 헤지의 목적은 두 가지입니다. 첫 번째는 주문 포지션을 헤지하는 것이고, 두 번째는 매수 가격과 매도 가격의 차이가 가능한 한 최대로 우리의 기대에 부합하도록 하는 것입니다. 이런 측면에서 보류 주문 거래의 장점은 거래 수수료가 낮다는 것입니다. 단점은 거래를 성사시키기 쉽지 않고, 한쪽에서만 거래가 성사되기 쉽다는 점입니다.

그런 다음 시장 A의 주문장에 매수 주문으로 매수 주문을 넣고, 시장 B의 주문장에 매도 주문으로 매도 주문을 넣는 거래 아이디어를 설계합니다. 그런 다음 계정의 보류 중인 주문을 확인하고 감지된 보류 중인 주문을 처리하는 다음 단계를 밟습니다. 예를 들어, 보류 중인 주문에 변화가 감지되면 선물 및 현물 헤지 포지션은 즉시 균형을 이루고, 선물 및 현물 포지션에서 초과된 포지션은 커버되거나 마감됩니다. 헤지 포지션의 증가에 따라, 다음 보류 주문과 시장의 첫 번째 계층 간의 거리를 조정하여 점진적으로 헤지하고 더 큰 가격 차이를 얻습니다.

헤지 로직 보류 주문 선물 헤지 전략 설계에 관한 연구 및 사례

코드 디자인

주석은 코드에 직접 작성됩니다. 이 예는 참조 설계에만 사용되며 OKEX V5 시뮬레이션 디스크에서 간략하게만 테스트되었습니다. 이 예시는 완전한 전략이 아니며 단지 참고용입니다.

// 临时参数
var fuContractType = "quarter"    // 期货合约
var fuSymbol = "ETH_USDT"         // 期货交易对
var spSymbol = "ETH_USDT"         // 现货交易对
var minAmount = 0.1               // 每次交易量、最小交易量,币数
var step = 40                     // 差价步长
var buff = 5                      // 缓冲差价
var balanceType = "open"          // 对于单腿成交平衡时, open 补仓 close 平仓

var depthManager = function(fuEx, spEx, fuCt, fuSymbol, spSymbol) {
    var self = {}
    self.fuExDepth = null
    self.spExDepth = null 
    self.plusPrice = null
    self.minusPrice = null 

    self.update = function() {        
        spEx.SetCurrency(spSymbol)
        if (!IsVirtual()) {
            fuEx.SetCurrency(fuSymbol)
        }        
        fuEx.SetContractType(fuCt)

        var fuRoutine = fuEx.Go("GetDepth")
        var spRoutine = spEx.Go("GetDepth")
        var fuDepth = fuRoutine.wait()
        var spDepth = spRoutine.wait()
        if (!fuDepth || !spDepth) {
            return false 
        }
        self.fuExDepth = fuDepth
        self.spExDepth = spDepth

        if (fuDepth.Bids.length == 0 || fuDepth.Asks.length == 0 || spDepth.Bids.length == 0 || spDepth.Asks.length == 0) {
            return false 
        }
        self.plusPrice = fuDepth.Bids[0].Price - spDepth.Asks[0].Price   // futures Bid - spot Ask
        self.minusPrice = fuDepth.Asks[0].Price - spDepth.Bids[0].Price  // futures Ask - spot Bid
        return true 
    }

    self.getData = function() {       
        return {
            "fuExDepth" : self.fuExDepth,
            "spExDepth" : self.spExDepth,
            "plusPrice" : self.plusPrice,
            "minusPrice" : self.minusPrice
        }
    }
    return self 
}

var positionManager = function(fuEx, spEx, fuCt, fuSymbol, spSymbol, step, buffDiff, balanceType, initSpAcc) {
    var self = {}
    self.balanceType = balanceType
    self.depth = null 
    self.level = 1
    self.lastUpdateTs = 0
    self.fuPos = []
    self.spPos = []
    self.initSpAcc = initSpAcc
    self.spAcc = null
    self.hedgePos = null
    self.hedgePosPrice = 0
    self.minAmount = 0.01
    self.offset = ["", 0]

    self.update = function() {
        spEx.SetCurrency(spSymbol)
        if (!IsVirtual()) {
            fuEx.SetCurrency(fuSymbol)
        }        
        fuEx.SetContractType(fuCt)

        self.offset = ["", 0]
        var fuRoutine = fuEx.Go("GetPosition")
        var spRoutine = spEx.Go("GetAccount")
        var fuPos = fuRoutine.wait()
        var spAcc = spRoutine.wait()
        if (!fuPos || !spAcc) {
            return false 
        }
        self.fuPos = fuPos
        self.spAcc = spAcc
        if (!self.initSpAcc) {
            return false 
        }
        self.spPos = (spAcc.Stocks + spAcc.FrozenStocks) - (self.initSpAcc.Stocks + self.initSpAcc.FrozenStocks)   // 当前减去最初,正数为做多
        // 检测fuPos
        if (fuPos.length > 1) {
            return false 
        }
        fuPosAmount = fuPos.length == 0 ? 0 : (fuPos[0].Type == PD_LONG ? fuPos[0].Amount : -fuPos[0].Amount)
        if ((fuPosAmount > 0 && self.spPos > 0) || (fuPosAmount < 0 && self.spPos < 0)) {
            return false 
        }

        fuPosAmount = self.piece2Coin(fuPosAmount)

        self.hedgePos = (fuPosAmount == 0 || self.spPos == 0) ? 0 : (fuPosAmount < 0 && self.spPos > 0 ? Math.min(Math.abs(fuPosAmount), Math.abs(self.spPos)) : -Math.min(Math.abs(fuPosAmount), Math.abs(self.spPos)))
        var diffBalance = (spAcc.Balance + spAcc.FrozenBalance) - (self.initSpAcc.Balance + self.initSpAcc.FrozenBalance)
        if (self.hedgePos == 0) {
            self.hedgePosPrice = 0    
        } else {
            self.hedgePosPrice = fuPos[0].Price - (Math.abs(diffBalance) / Math.abs(self.spPos))
        }
        self.offset[1] = fuPosAmount + self.spPos  // 正数多头持仓溢出,负数空头持仓溢出
        if (fuPosAmount > 0 && self.spPos < 0) {   // 反套
            self.offset[0] = "minus"
        } else if (fuPosAmount < 0 && self.spPos > 0) {
            self.offset[0] = "plus"
        } else if (fuPosAmount == 0 && self.spPos < 0) {
            self.offset[0] = "minus"
        } else if (fuPosAmount > 0 && self.spPos == 0) {
            self.offset[0] = "minus"
        } else if (fuPosAmount == 0 && self.spPos > 0) {
            self.offset[0] = "plus"
        } else if (fuPosAmount < 0 && self.spPos == 0) {
            self.offset[0] = "plus"
        }
        return true 
    }

    self.getData = function() {
        return {
            "fuPos" : self.fuPos,
            "spPos" : self.spPos,
            "initSpAcc" : self.initSpAcc,
            "spAcc" : self.spAcc,
            "hedgePos" : self.hedgePos,
            "hedgePosPrice" : self.hedgePosPrice,
        }
    }

    self.keepBalance = function(depth) {
        var fuDepth = depth.fuExDepth
        var spDepth = depth.spExDepth
        if (self.offset[0] == "plus") {
            if (self.offset[1] >= self.minAmount) {
                if (self.balanceType == "close") {
                    // 现货多头持仓多了,平现货多仓
                    spEx.Sell(-1, self.offset[1])
                } else if (self.balanceType == "open") {
                    // 现货多头持仓多了,开期货空头持仓
                    fuEx.SetDirection("sell")
                    fuEx.Sell(-1, self.coin2Piece(Math.abs(self.offset[1])))
                }
            } else if (self.offset[1] <= -self.minAmount) {
                if (self.balanceType == "close") {
                    // 期货空头持仓多了,平期货空仓
                    fuEx.SetDirection("closesell")
                    fuEx.Buy(-1, self.coin2Piece(Math.abs(self.offset[1])))
                } else if (self.balanceType == "open") {
                    // 期货空头持仓多了,开现货多头持仓
                    spEx.Buy(-1, spDepth.Asks[0].Price * Math.abs(self.offset[1]))
                }
            }
            return false 
        } else if (self.offset[0] == "minus") {
            if (self.offset[1] >= self.minAmount) {
                if (self.balanceType == "close") {
                    // 期货多头持仓多了,平期货多仓
                    fuEx.SetDirection("closebuy")
                    fuEx.Sell(-1, self.coin2Piece(self.offset[1]))
                } else if (self.balanceType == "open") {
                    // 期货多头持仓多了,开现货空头持仓
                    spEx.Sell(-1, self.offset[1])
                }
            } else if (self.offset[1] <= -self.minAmount) {
                if (self.balanceType == "close") {
                    // 现货空头持仓多了,平现货空仓
                    spEx.Buy(-1, spDepth.Asks[0].Price * Math.abs(self.offset[1]))
                } else if (self.balanceType == "open") {
                    // 现货空头持仓多了,开期货多头持仓
                    fuEx.SetDirection("buy")
                    fuEx.Buy(-1, self.coin2Piece(Math.abs(self.offset[1])))
                }
            }
            return false 
        }
        return true 
    }

    self.process = function(depthManager) {
        var ts = new Date().getTime()
        var depth = depthManager.getData()
        var orders = self.getOrders()
        if (!orders) {
            return 
        }
        self.depth = depth
        var fuOrders = orders[0]
        var spOrders = orders[1]
        
        if (fuOrders.length == 0 && spOrders.length == 0) {
            // 重置level
            if (self.hedgePos == 0) {
                self.level = 1
            } else {
                self.level = Math.max(1, _N(self.hedgePos / self.minAmount, 0))
            }

            // 限制最大持仓量
            if (Math.abs(self.hedgePos) > 1) {
                return 
            }

            // 挂单
            var fuDepth = depth.fuExDepth
            var spDepth = depth.spExDepth
            self.update()

            if (self.hedgePos >= 0 && fuDepth.Bids[0].Price - spDepth.Asks[0].Price > 0) {        // 正套
                var distance = (step * self.level - (fuDepth.Asks[0].Price - spDepth.Bids[0].Price)) / 2          
                fuEx.SetDirection("sell")
                fuEx.Sell(fuDepth.Asks[0].Price + distance, self.coin2Piece(self.minAmount), fuDepth.Asks[0].Price, "挂单差价:", fuDepth.Asks[0].Price + distance - (spDepth.Bids[0].Price - distance))
                spEx.Buy(spDepth.Bids[0].Price - distance, self.minAmount, spDepth.Bids[0].Price)
            } else if (self.hedgePos <= 0 && spDepth.Bids[0].Price - fuDepth.Asks[0].Price > 0) { // 反套
                var distance = (step * self.level - (spDepth.Asks[0].Price - fuDepth.Bids[0].Price)) / 2          
                fuEx.SetDirection("buy")
                fuEx.Buy(fuDepth.Bids[0].Price - distance, self.coin2Piece(self.minAmount), fuDepth.Bids[0].Price, "挂单差价:", spDepth.Asks[0].Price + distance - (fuDepth.Bids[0].Price - distance))
                spEx.Sell(spDepth.Asks[0].Price + distance, self.minAmount, spDepth.Asks[0].Price)
            }
        } else if (fuOrders.length == 1 && spOrders.length == 1) {
            var fuDepth = depth.fuExDepth
            var spDepth = depth.spExDepth            
            // 判断位置
            var isCancelAll = false 
            if (self.hedgePos >= 0 && fuDepth.Bids[0].Price - spDepth.Asks[0].Price > 0) {        // 正套
                var distance = (step * self.level - (fuDepth.Asks[0].Price - spDepth.Bids[0].Price)) / 2
                if (Math.abs(fuOrders[0].Price - (fuDepth.Asks[0].Price + distance)) > buffDiff || Math.abs(spOrders[0].Price - (spDepth.Bids[0].Price - distance)) > buffDiff) {
                    isCancelAll = true 
                }
            } else if (self.hedgePos <= 0 && spDepth.Bids[0].Price - fuDepth.Asks[0].Price > 0) { // 反套
                var distance = (step * self.level - (spDepth.Asks[0].Price - fuDepth.Bids[0].Price)) / 2
                if (Math.abs(spOrders[0].Price - (spDepth.Asks[0].Price + distance)) > buffDiff || Math.abs(fuOrders[0].Price - (fuDepth.Bids[0].Price - distance)) > buffDiff) {
                    isCancelAll = true 
                }
            } else {
                isCancelAll = true 
            }
            if (isCancelAll) {
                self.cancelAll(fuEx, fuOrders)
                self.cancelAll(spEx, spOrders)
                self.lastUpdateTs = 0
            }
        } else {            
            self.cancelAll(fuEx, fuOrders)
            self.cancelAll(spEx, spOrders)       
            self.lastUpdateTs = 0
        }

        if (ts - self.lastUpdateTs > 1000 * 60 * 2) {
            self.update()
            self.keepBalance(depth)
            self.update()
            self.lastUpdateTs = ts 
        }
        LogStatus(_D())   // 状态栏可以设计输出需要观察的数据、信息
    }

    self.getOrders = function() {
        spEx.SetCurrency(spSymbol)
        if (!IsVirtual()) {
            fuEx.SetCurrency(fuSymbol)
        }        
        fuEx.SetContractType(fuCt)

        var fuRoutine = fuEx.Go("GetOrders")
        var spRoutine = spEx.Go("GetOrders")
        var fuOrders = fuRoutine.wait()
        var spOrders = spRoutine.wait()
        if (!fuOrders || !spOrders) {
            return false 
        }
        return [fuOrders, spOrders]
    }
    
    // 币转合约张数
    self.coin2Piece = function(amount) {
        if (IsVirtual()) {
            if (fuEx.GetName() == "Futures_Binance") {
                return amount
            } else if (fuEx.GetName() == "Futures_OKCoin") {
                var price = (self.depth.fuExDepth.Bids[0].Price + self.depth.fuExDepth.Asks[0].Price) / 2
                return _N(amount / (100 / price), 0)
            } else {
                throw "not support"
            }            
        }
        if (fuEx.GetName() == "Futures_OKCoin") {
            if (fuEx.GetQuoteCurrency() == "USDT") {
                return _N(amount * 10, 0)
            } else if (fuEx.GetQuoteCurrency() == "USD") {
                var price = (self.depth.fuExDepth.Bids[0].Price + self.depth.fuExDepth.Asks[0].Price) / 2
                return _N(amount / (100 / price), 0)
            } else {
                throw "not support"
            }
        } else {
            throw "not support"
        }
    }
    
    // 合约张数转币
    self.piece2Coin = function(amount) {
        if (IsVirtual()) {
            if (fuEx.GetName() == "Futures_Binance") {
                return amount
            } else if (fuEx.GetName() == "Futures_OKCoin") {
                var price = (self.depth.fuExDepth.Bids[0].Price + self.depth.fuExDepth.Asks[0].Price) / 2
                return amount * 100 / price
            } else {
                throw "not support"
            }            
        }
        if (fuEx.GetName() == "Futures_OKCoin") {
            if (fuEx.GetQuoteCurrency() == "USDT") {
                return amount * 0.1
            } else if (fuEx.GetQuoteCurrency() == "USD") {
                var price = (self.depth.fuExDepth.Bids[0].Price + self.depth.fuExDepth.Asks[0].Price) / 2
                return amount * 100 / price
            } else {
                throw "not support"
            }
        } else {
            throw "not support"
        }
    }

    self.cancelAll = function(e, orders) {
        var isFirst = true 
        while (true) {
            Sleep(500)
            if (orders && isFirst) {
                isFirst = false 
            } else {
                orders = e.GetOrders()
            }
            if (!orders) {
                continue
            } else {
                for (var i = 0 ; i < orders.length ; i++) {
                    e.CancelOrder(orders[i].Id, orders[i])
                }
            }
            if (orders.length == 0) {
                break
            }
        }
    }

    self.CoverAll = function() {
        // 全部平仓
        // 这里可以实现一个,一键平仓的功能
    }

    self.setMinAmount = function(minAmount) {
        self.minAmount = minAmount
    }

    self.init = function() {
        while(!self.spAcc) {
            self.update()
            Sleep(1000)
        }
        if (!self.initSpAcc) {  
            var positionManager_initSpAcc = _G("positionManager_initSpAcc")
            if (!positionManager_initSpAcc) {
                self.initSpAcc = self.spAcc
                _G("positionManager_initSpAcc", self.initSpAcc)
            } else {
                self.initSpAcc = positionManager_initSpAcc
            }
        } else {
            _G("positionManager_initSpAcc", self.initSpAcc)
        }
        // 打印初始信息
        Log("self.initSpAcc:", self.initSpAcc.Balance, self.initSpAcc.FrozenBalance, self.initSpAcc.Stocks, self.initSpAcc.FrozenStocks)
    }
    self.init()
    return self
}

function main() {
    _G(null)       // 清空持久化数据
    LogReset(1)    // 重置日志

    // 以下代码可以切换OKEX模拟盘
    // exchanges[0].IO("simulate", true)
    // exchanges[1].IO("simulate", true)

    var dm = depthManager(exchanges[0], exchanges[1], fuContractType, fuSymbol, spSymbol)
    var pm = positionManager(exchanges[0], exchanges[1], fuContractType, fuSymbol, spSymbol, step, buff, balanceType)
    pm.setMinAmount(minAmount)

    while (true) {
        if (!dm.update()) {
            Sleep(3000)
            continue
        }

        var cmd = GetCommand()
        if (cmd) {
            // 处理交互
            Log("交互命令:", cmd)
            var arr = cmd.split(":") 
            if (arr[0] == "") {
                pm.CoverAll()
            }            
        }

        pm.process(dm)
        Sleep(5000)
    }
}

백테스트 분석

보류 주문 선물 헤지 전략 설계에 관한 연구 및 사례

보류 주문 선물 헤지 전략 설계에 관한 연구 및 사례

주문이 들어오고 취소되는 빈도가 매우 높은 것을 알 수 있습니다. 백테스팅 시스템을 통해 계산된 수익을 판단해보면, 선물 거래 계좌에서는 -0.01666 ETH가 손실되었고, 현물 거래에서는 842.23758 USDT의 수익을 냈습니다. 백테스트가 종료된 현재 ETH 현물 가격은 4,252USDT였습니다.-0.01666 * 4252 = -70.83832000000001. 현물 이익과 합치면 전반적인 상황은 수익성이 있습니다.

하지만 이는 단지 백테스트일 뿐이며, 더 많은 세부 사항은 실제 거래에서 해결되어야 합니다.