وسائل لوڈ ہو رہے ہیں... لوڈنگ...

میکر اسپاٹ اور فیوچر ہیجنگ حکمت عملی ڈیزائن پر تحقیق اور مثال

مصنف:FMZ~Lydia, تخلیق: 2022-11-09 14:49:29, تازہ کاری: 2023-09-14 20:38:22

img

طویل عرصے سے ، فیوچر اور اسپاٹ ہیجنگ عام طور پر قیمت کے فرق کا پتہ لگانے کے لئے ڈیزائن کیا گیا ہے۔ جب قیمت کا فرق پورا ہوجاتا ہے تو ، ہم ہیج کرنے کے لئے آرڈر لیں گے۔ کیا اسے میکر ہیجنگ کے طور پر ڈیزائن کیا جاسکتا ہے؟ جواب بالکل ہاں ہے۔ آج ، میں آپ کو میکر ہیجنگ کے لئے ڈیزائن آئیڈیا اور کوڈ پروٹو ٹائپ لاتا ہوں۔

میکر ہیجنگ کا خیال

ایک ہی یا ایک ہی قسم کے موضوع کے مختلف بازاروں میں ، ہیجنگ کے مواقع اس وقت پیدا ہوتے ہیں جب دو مارکیٹوں کے فروخت کے احکامات اور خریداری کے احکامات کے مابین بہت بڑا فرق ہوتا ہے۔ عام طور پر ، ہم ایسے میکرز کریں گے جو قیمت کے فرق کو پورا کرتے ہیں اور ہیجنگ پوزیشن رکھتے ہیں۔ لہذا ، ہیجنگ کے دو مقاصد ہیں۔ پہلا پوزیشن کو ہیج کرنا ہے اور دوسرا یہ یقینی بنانا ہے کہ خرید و فروخت کے آرڈر کے مابین فرق ہماری توقعات کو زیادہ سے زیادہ حد تک پورا کرتا ہے۔ اس سلسلے میں میکر ٹریڈنگ کا فائدہ یہ ہے کہ کمیشن فیس کم ہے۔ نقصان یہ ہے کہ یہ سودا کرنا آسان نہیں ہے ، اور ایک ہی پوزیشن پر سودا کرنا آسان ہے۔

تجارتی خیال جو ہم ڈیزائن کرتے ہیں وہ یہ ہے کہ مارکیٹ آرڈر بک اے میں خرید آرڈر اور مارکیٹ آرڈر بک بی میں فروخت آرڈر رکھیں۔ پھر ہم اپنے اکاؤنٹ کے زیر التواء آرڈرز کی جانچ کرتے ہیں ، اور زیر التواء آرڈر ٹرانزیکشنز کے لئے اگلا مرحلہ کرتے ہیں۔ مثال کے طور پر ، اگر ہمیں زیر التواء آرڈر میں تبدیلی مل جاتی ہے تو ، ہم فوری طور پر اسپاٹ اور فیوچر کی ہیج پوزیشن کو متوازن کرتے ہیں ، اور اسپاٹ اور فیوچر پوزیشن میں اوور فلو پوزیشن کو ڈھکتے یا بند کرتے ہیں۔ ہیجنگ پوزیشن میں اضافے کے مطابق ، ہم آرڈر میں پہلے زیر التواء آرڈر کی دوری کو آرڈر میں اگلی پوزیشن تک ایڈجسٹ کرتے ہیں ، اور آہستہ آہستہ زیادہ پھیلاؤ حاصل کرنے کے لئے ہیج کرتے ہیں۔

کوڈ ڈیزائن

تبصرے کوڈ میں براہ راست لکھے جاتے ہیں۔ مثال صرف حوالہ ڈیزائن کے لئے استعمال ہوتی ہے اور اس کا 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)
    }
}

بیک ٹسٹ تجزیہ

img img img img

ہم دیکھ سکتے ہیں کہ زیر التواء آرڈرز اور انخلا کے آرڈر زیادہ ہیں۔ بیک ٹیسٹنگ سسٹم کے اعدادوشمار سے ، فیوچر ایکسچینج اکاؤنٹ نے -0.01666 ETH کھوئے اور اسپاٹ ایکسچینج نے 842.23758 USDT کا منافع کمایا۔ بیک ٹیسٹ کے اختتام پر ETH اسپاٹ قیمت 4252 USDT تھی ، -0.01666 * 4252 = -70.83832000000001۔ اس کے علاوہ اسپاٹ منافع کے بعد مجموعی طور پر منافع حاصل ہوتا ہے۔

لیکن یہ صرف ایک بیک ٹیسٹ پر ہے، اور یقینی طور پر حقیقی بوٹ میں کام کرنے کے لئے مزید تفصیلات ہیں.


متعلقہ

مزید