রিসোর্স লোড হচ্ছে... লোডিং...

মেকার স্পট এবং ফিউচার হেজিং স্ট্র্যাটেজি ডিজাইনের উপর গবেষণা এবং উদাহরণ

লেখক:এফএমজেড-লিডিয়া, সৃষ্টিঃ ২০২২-১১-০৯ 14:49:29, আপডেটঃ ২০২৩-০৯-১৪ 20:38:22

img

দীর্ঘদিন ধরে, ফিউচার এবং স্পট হেজিং সাধারণত মূল্য পার্থক্য সনাক্ত করার জন্য ডিজাইন করা হয়। যখন মূল্য পার্থক্য পূরণ করা হয়, আমরা হেজিংয়ের অর্ডার নেব। এটি প্রস্তুতকারক হেজিং হিসাবে ডিজাইন করা যেতে পারে? উত্তরটি একেবারে হ্যাঁ। আজ, আমি আপনাকে একটি ডিজাইন ধারণা এবং কোড প্রোটোটাইপ নিয়ে আসব।

মেকার হেজিং এর ধারণা

একই বা একই ধরণের বিষয়বস্তুর বিভিন্ন বাজারে, যখন দুটি বাজারের বিক্রয় আদেশ এবং ক্রয়ের আদেশের মধ্যে একটি বড় পার্থক্য থাকে তখন হেজিংয়ের সুযোগগুলি উত্থাপিত হয়। সাধারণভাবে, আমরা এমন নির্মাতারা করব যা মূল্যের পার্থক্য পূরণ করে এবং হেজিং পজিশন ধরে রাখে। অতএব, হেজিংয়ের জন্য দুটি উদ্দেশ্য রয়েছে। প্রথমটি হ'ল অবস্থানটি হেজিং করা এবং দ্বিতীয়টি হ'ল নিশ্চিত করা যে ক্রয় এবং বিক্রয় আদেশের মধ্যে পার্থক্যটি আমাদের প্রত্যাশাগুলি সর্বাধিক পরিমাণে পূরণ করে। এই ক্ষেত্রে মেকার ট্রেডিংয়ের সুবিধা হ'ল কমিশন ফি কম। অসুবিধাটি হ'ল চুক্তি করা সহজ নয় এবং একক অবস্থানে চুক্তি করা সহজ।

ট্রেডিং আইডিয়া আমরা ডিজাইন করা হয় একটি কিনতে অর্ডার A বাজার অর্ডার বই এবং একটি বিক্রয় অর্ডার বি বাজার অর্ডার বই মধ্যে স্থাপন করা হয়। তারপর আমরা আমাদের অ্যাকাউন্ট মুলতুবি আদেশ চেক, এবং চেক মুলতুবি অর্ডার লেনদেনের জন্য পরবর্তী ধাপ করতে। উদাহরণস্বরূপ, আমরা যদি মুলতুবি অর্ডার একটি পরিবর্তন খুঁজে, আমরা অবিলম্বে স্পট এবং ফিউচার হেজ অবস্থান ভারসাম্য, এবং কভার বা বন্ধ স্পট এবং ফিউচার অবস্থান মধ্যে ওভারফ্লো অবস্থান। হেজিং অবস্থান বৃদ্ধি অনুযায়ী, আমরা অর্ডার পরবর্তী অবস্থানে অর্ডার প্রথম মুলতুবি অর্ডার দূরত্ব সমন্বয়, এবং ধীরে ধীরে একটি বৃহত্তর স্প্রেড পেতে হেজ।

কোড ডিজাইন

মন্তব্যগুলি সরাসরি কোডে লেখা হয়। উদাহরণটি শুধুমাত্র রেফারেন্স ডিজাইনের জন্য ব্যবহৃত হয় এবং এটি 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 ইউএসডিটি মুনাফা করেছে। ব্যাকটেস্টিংয়ের শেষে ইটিএইচ স্পট মূল্য ছিল 4252 ইউএসডিটি, -0.01666 * 4252 = -70.83832000000001. এটি স্পট মুনাফা যোগ করার পরে সামগ্রিক মুনাফা অর্জন করে।

কিন্তু এটা শুধু ব্যাকটেস্টে, এবং আসল বটে অবশ্যই আরো বিস্তারিত কাজ করা আছে।


সম্পর্কিত

আরো