오랜 시간 동안 선물 및 스팟 헤딩은 일반적으로 가격 차이를 감지하기 위해 설계되었습니다. 가격 차이가 충족되면, 우리는 헤딩에 주문을합니다. 그것은 메이커 헤딩으로 설계 될 수 있습니까? 대답은 절대적으로 그렇습니다. 오늘은 메이커 헤딩에 대한 디자인 아이디어와 코드 프로토 타입을 제공합니다.
같은 또는 같은 유형의 주제의 다른 시장에서 두 시장의 판매 주문과 구매 주문 사이에 큰 차이가있을 때 헤지 기회가 발생합니다. 일반적으로 우리는 가격 차이를 충족하고 헤지 포지션을 보유하는 메이커를 할 것입니다. 따라서 헤지에는 두 가지 목적이 있습니다. 첫째는 포지션을 헤지하고 둘째는 구매 및 판매 주문 사이의 차이가 우리의 기대를 최대한 충족시키는 것을 보장하는 것입니다. 이 점에서 메이커 거래의 장점은 수수료가 낮다는 것입니다. 단점은 거래를하는 것이 쉽지 않으며 단일 위치에서 거래를하는 것이 쉽다는 것입니다.
우리가 설계하는 거래 아이디어는 A 시장 주문 책에 구매 주문과 B 시장 주문 책에 판매 주문을 배치하는 것입니다. 그런 다음 우리는 우리의 계정을 대기 주문을 확인하고, 확인 된 대기 주문 거래에 대한 다음 단계를 수행합니다. 예를 들어, 대기 주문에서 변화를 발견하면 즉시 스팟과 선물의 헤지 포지션을 균형을 맞추고, 스팟과 선물 포지션의 오버플로우 포지션을 커버하거나 닫습니다. 헤지 포지션의 증가에 따라, 우리는 순서에서 첫 번째 대기 주문의 거리를 순서에서 다음 위치로 조정하고, 점차적으로 더 큰 스프레드를 얻기 위해 헤지합니다.
댓글은 코드에 직접 작성됩니다. 예제는 참조 디자인만을 위해 사용되며 OKEX V5 데모에서 테스트되었습니다. 예제는 완벽한 전략이 아닙니다. 참조를 위해만 사용하십시오.
// Temporary parameters
var fuContractType = "quarter" // Futures contracts
var fuSymbol = "ETH_USDT" // Futures trading pairs
var spSymbol = "ETH_USDT" // Spots trading pairs
var minAmount = 0.1 // Amount per transaction, minimum transaction amount, currency
var step = 40 // Difference step length
var buff = 5 // Buffer price difference
var balanceType = "open" // When the single position transaction is balanced, open the covering position and close the closing position
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) // Current one minus the initial one, positive number means going long
// Check 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 positive, long positions overflow, if negative, short positions overflow
if (fuPosAmount > 0 && self.spPos < 0) { // Reverse arbitrage
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") {
// If the spot long position is excessive, close the spot long position
spEx.Sell(-1, self.offset[1])
} else if (self.balanceType == "open") {
// If the spot long position is excessive, open the future short position
fuEx.SetDirection("sell")
fuEx.Sell(-1, self.coin2Piece(Math.abs(self.offset[1])))
}
} else if (self.offset[1] <= -self.minAmount) {
if (self.balanceType == "close") {
// If the future short position is excessive, close the future short position
fuEx.SetDirection("closesell")
fuEx.Buy(-1, self.coin2Piece(Math.abs(self.offset[1])))
} else if (self.balanceType == "open") {
// If the future short position is excessive, open the spot long position
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") {
// If the future long position is excessive, close the future long position
fuEx.SetDirection("closebuy")
fuEx.Sell(-1, self.coin2Piece(self.offset[1]))
} else if (self.balanceType == "open") {
// If the future long position is excessive, open the spot short position
spEx.Sell(-1, self.offset[1])
}
} else if (self.offset[1] <= -self.minAmount) {
if (self.balanceType == "close") {
// If the spot short position is excessive, close the spot short position
spEx.Buy(-1, spDepth.Asks[0].Price * Math.abs(self.offset[1]))
} else if (self.balanceType == "open") {
// If the spot short position is excessive, open the future long position
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) {
// Reset level
if (self.hedgePos == 0) {
self.level = 1
} else {
self.level = Math.max(1, _N(self.hedgePos / self.minAmount, 0))
}
// Limit the maximum position
if (Math.abs(self.hedgePos) > 1) {
return
}
// Pending orders
var fuDepth = depth.fuExDepth
var spDepth = depth.spExDepth
self.update()
if (self.hedgePos >= 0 && fuDepth.Bids[0].Price - spDepth.Asks[0].Price > 0) { // Positive arbitrage
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, "Price difference of makers:", 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) { // Reverse arbitrage
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, "Price difference of makers:", 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
// Judge the position
var isCancelAll = false
if (self.hedgePos >= 0 && fuDepth.Bids[0].Price - spDepth.Asks[0].Price > 0) { // Positive arbitrage
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) { // Reverse arbitrage
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()) // The status bar can be designed to output the data and information to be observed
}
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]
}
// Number of currency converted into contracts
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"
}
}
// Number of contracts converted into currency
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() {
// Close all positions
// Here we can realize one-click position closing
}
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)
}
// Print the initial information
Log("self.initSpAcc:", self.initSpAcc.Balance, self.initSpAcc.FrozenBalance, self.initSpAcc.Stocks, self.initSpAcc.FrozenStocks)
}
self.init()
return self
}
function main() {
_G(null) // Clear the persistent data
LogReset(1) // Reset logs
// The following code can be switchedto the OKEX Demo
// 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) {
// Handle interactions
Log("Interaction command:", cmd)
var arr = cmd.split(":")
if (arr[0] == "") {
pm.CoverAll()
}
}
pm.process(dm)
Sleep(5000)
}
}
우리는 대기 주문과 인출 주문이 더 많다는 것을 볼 수 있습니다. 백테스팅 시스템
하지만 이것은 단지 백테스트에 불과합니다. 그리고 실제 봇에 더 많은 세부사항이 있습니다.