0bps

Strategy vault / Other

Spot Hedging Strategy with Different Quote Currencies Ver11

Related articles:https://www.fmz.com/digest-topic/7666

JavaScriptOther
Author
发明者量化-小小梦
Complexity
Advanced · 284 lines
Parameters
21

Spot hedging strategies for different denominated currencies Ver1.1

Related articles:https://www.fmz.com/digest-topic/7666

Parameters

NameDefaultWhat it controls
keepBalanceCyc300Balance Cycle
diffAsPercentagetrueUse spread percentage
hedgeDiffPriceA2B20Hedging the differenceAtoB
hedgeDiffPriceB2A20Hedging the differenceBtoA
hedgeDiffPercentageA2B4Hedging spread percentageAtoB
hedgeDiffPercentageB2A4Hedging spread percentageBtoA
minHedgeAmount0.005Minimum order amount for hedging
maxHedgeAmount0.2Hedging maximum order amount
rateAtrueAExchange exchange rate
rateBtrueBExchange exchange rate
isResetfalseReset all information
pricePrecisionA2APrice Precision
amountPrecisionA3AOrder quantity accuracy
pricePrecisionB2BPrice Precision
amountPrecisionB3BOrder quantity accuracy
slidePricetrueOrder Slippage
marginType0Leverage type: Regular crypto | Isolated leverage | Cross leverage
ButtonDefaultDescription
------------
A2BfalseModify parameters from A to B
B2AfalseModify parameters from B to A

Source JavaScript

var lastKeepBalanceTS = 0

function hedge(buyEx, sellEx, priceBuy, priceSell, amount) {
    var buyRoutine = buyEx.Go("Buy", priceBuy, amount)
    var sellRoutine = sellEx.Go("Sell", priceSell, amount)
    Sleep(500)
    buyRoutine.wait()
    sellRoutine.wait()
}

function getDepthPrice(depth, side, amount) {
    var arr = depth[side]
    var sum = 0
    var price = null
    for (var i = 0 ; i < arr.length ; i++) {
        var ele = arr[i]
        sum += ele.Amount
        if (sum >= amount) {
            price = ele.Price
            break
        }
    }
    return price
}

function keepBalance(initAccs, nowAccs, depths) {
    var initSumStocks = 0
    var nowSumStocks = 0 
    _.each(initAccs, function(acc) {
        initSumStocks += acc.Stocks + acc.FrozenStocks
    })
    _.each(nowAccs, function(acc) {
        nowSumStocks += acc.Stocks + acc.FrozenStocks
    })
    
    var diff = nowSumStocks - initSumStocks
    // Calculate Coin Spread
    if (Math.abs(diff) > minHedgeAmount && initAccs.length == nowAccs.length && nowAccs.length == depths.length) {
        Log("Trigger balancing operation, balance amount:", Math.abs(diff))
        var index = -1
        var available = []
        var side = diff > 0 ? "Bids" : "Asks"
        for (var i = 0 ; i < nowAccs.length ; i++) {
            var price = getDepthPrice(depths[i], side, Math.abs(diff))
            if (side == "Bids" && nowAccs[i].Stocks * 0.9 > Math.abs(diff)) {
                available.push(i)
            } else if (side == "Asks" && price && nowAccs[i].Balance / price * 0.9 > Math.abs(diff)) {
                available.push(i)
            }
        }
        for (var i = 0 ; i < available.length ; i++) {
            if (index == -1) {
                index = available[i]
            } else {
                var priceIndex = getDepthPrice(depths[index], side, Math.abs(diff))
                var priceI = getDepthPrice(depths[available[i]], side, Math.abs(diff))
                if (side == "Bids" && priceIndex && priceI && priceI > priceIndex) {
                    index = available[i]
                } else if (side == "Asks" && priceIndex && priceI && priceI < priceIndex) {
                    index = available[i]
                }
            }
        }
        if (index == -1) {
            Log("Unable to Balance")            
        } else {
            // Balance Order
            var price = getDepthPrice(depths[index], side, Math.abs(diff))
            if (price) {
                var tradeFunc = side == "Bids" ? exchanges[index].Sell : exchanges[index].Buy
                tradeFunc(price, Math.abs(diff))
            } else {
                Log("Invalid Price", price)
            }
        }        
        return false
    } else if (!(initAccs.length == nowAccs.length && nowAccs.length == depths.length)) {
        Log("Error:", "initAccs.length:", initAccs.length, "nowAccs.length:", nowAccs.length, "depths.length:", depths.length)
        return true 
    } else {
        return true 
    }
}

function cancelAll() {
    _.each(exchanges, function(ex) {
        while (true) {
            var orders = _C(ex.GetOrders)
            if (orders.length == 0) {
                break
            }
            for (var i = 0 ; i < orders.length ; i++) {
                ex.CancelOrder(orders[i].Id, orders[i])
                Sleep(500)
            }
        }
    })
}

function updateAccs(arrEx) {
    var ret = []
    for (var i = 0 ; i < arrEx.length ; i++) {
        var acc = arrEx[i].GetAccount()
        if (!acc) {
            return null
        }
        ret.push(acc)
    }
    return ret 
}

function main() {
    var exA = exchanges[0]
    var exB = exchanges[1]
    // Precision, exchange rate settings
    if (rateA != 1) {
        // Set Exchange RateA
        exA.SetRate(rateA)
        Log("exchangeASet Exchange Rate:", rateA, "#FF0000")
    }
    if (rateB != 1) {
        // Set Exchange RateB
        exB.SetRate(rateB)
        Log("exchangeBSet Exchange Rate:", rateB, "#FF0000")
    }
    exA.SetPrecision(pricePrecisionA, amountPrecisionA)
    exB.SetPrecision(pricePrecisionB, amountPrecisionB)

    // Switch leverage mode
    for (var i = 0 ; i < exchanges.length ; i++) {
        if (exchanges[i].GetName() == "Binance" && marginType != 0) {
            if (marginType == 1) {
                Log(exchanges[i].GetName(), "Set to leverage isolated position")
                exchanges[i].IO("trade_margin")
            } else if (marginType == 2) {
                Log(exchanges[i].GetName(), "Set to leveraged full position")
                exchanges[i].IO("trade_super_margin")
            }
        }
    }
    
    if (isReset) {
        _G(null)
        LogReset(1)
        LogProfitReset()
        LogVacuum()
        Log("Reset all data", "#FF0000")
    }

    var nowAccs = _C(updateAccs, exchanges)
    var initAccs = _G("initAccs")
    if (!initAccs) {
        initAccs = nowAccs
        _G("initAccs", initAccs)
    }

    var isTrade = false 
    while (true) {
        var ts = new Date().getTime()
        var depthARoutine = exA.Go("GetDepth")
        var depthBRoutine = exB.Go("GetDepth")
        var depthA = depthARoutine.wait()
        var depthB = depthBRoutine.wait()
        if (!depthA || !depthB || depthA.Asks.length == 0 || depthA.Bids.length == 0 || depthB.Asks.length == 0 || depthB.Bids.length == 0) {
            Sleep(500)
            continue 
        }

        var targetDiffPriceA2B = hedgeDiffPriceA2B
        var targetDiffPriceB2A = hedgeDiffPriceB2A
        if (diffAsPercentage) {
            targetDiffPriceA2B = (depthA.Bids[0].Price + depthB.Asks[0].Price + depthB.Bids[0].Price + depthA.Asks[0].Price) / 4 * (hedgeDiffPercentageA2B / 100)
            targetDiffPriceB2A = (depthA.Bids[0].Price + depthB.Asks[0].Price + depthB.Bids[0].Price + depthA.Asks[0].Price) / 4 * (hedgeDiffPercentageB2A / 100)
        }

        // Drawing
        $.PlotHLine(targetDiffPriceA2B, "A->B")
        $.PlotHLine(targetDiffPriceB2A, "B->A")

        if (depthA.Bids[0].Price - depthB.Asks[0].Price > targetDiffPriceA2B && Math.min(depthA.Bids[0].Amount, depthB.Asks[0].Amount) >= minHedgeAmount) {          // A -> B Handicap conditions are met            
            var priceSell = depthA.Bids[0].Price - slidePrice
            var priceBuy = depthB.Asks[0].Price + slidePrice
            
            // Handling negative precision parameters
            if (pricePrecisionA < 0) {
                // priceSell = _N(priceSell, pricePrecisionA) - slidePrice
                priceSell = _N(priceSell - slidePrice, pricePrecisionA)
                // Log("priceSell:", priceSell, "priceSell - slidePrice:", priceSell - slidePrice, "pricePrecisionA:", pricePrecisionA, typeof(pricePrecisionA)) // Test
            }
            if (pricePrecisionB < 0) {
                // priceBuy = _N(priceBuy, pricePrecisionB) + slidePrice
                priceBuy = _N(priceBuy + slidePrice, pricePrecisionB)
                // Log("priceBuy:", priceBuy, "priceBuy + slidePrice:", priceBuy + slidePrice, "pricePrecisionB:", pricePrecisionB, typeof(pricePrecisionB)) // Test
            }
            
            var amount = Math.min(depthA.Bids[0].Amount, depthB.Asks[0].Amount)
            if (nowAccs[0].Stocks > minHedgeAmount && nowAccs[1].Balance * 0.8 / priceSell > minHedgeAmount) {
                amount = Math.min(amount, nowAccs[0].Stocks, nowAccs[1].Balance * 0.8 / priceSell, maxHedgeAmount)
                Log("TriggerA->B:", depthA.Bids[0].Price - depthB.Asks[0].Price, priceBuy, priceSell, amount, nowAccs[1].Balance * 0.8 / priceSell, nowAccs[0].Stocks)  // Prompt Information
                hedge(exB, exA, priceBuy, priceSell, amount)
                cancelAll()
                lastKeepBalanceTS = 0
                isTrade = true 
            }            
        } else if (depthB.Bids[0].Price - depthA.Asks[0].Price > targetDiffPriceB2A && Math.min(depthB.Bids[0].Amount, depthA.Asks[0].Amount) >= minHedgeAmount) {   // B -> A Handicap conditions are met
            var priceBuy = depthA.Asks[0].Price + slidePrice
            var priceSell = depthB.Bids[0].Price - slidePrice
            
            // Handling negative precision parameters
            if (pricePrecisionA < 0) {
                // priceBuy = _N(priceBuy, pricePrecisionA) + slidePrice
                priceBuy = _N(priceBuy  + slidePrice, pricePrecisionA)
                // Log("priceBuy:", priceBuy, "priceBuy  + slidePrice:", priceBuy  + slidePrice, "pricePrecisionA:", pricePrecisionA, typeof(pricePrecisionA))  // Test
            }
            if (pricePrecisionB < 0) {
                // priceSell = _N(priceSell, pricePrecisionB) - slidePrice
                priceSell = _N(priceSell - slidePrice, pricePrecisionB)
                // Log("priceSell:", priceSell, "priceSell - slidePrice:", priceSell - slidePrice, "pricePrecisionB:", pricePrecisionB, typeof(pricePrecisionB)) // Test
            }
            
            var amount = Math.min(depthB.Bids[0].Amount, depthA.Asks[0].Amount)
            if (nowAccs[1].Stocks > minHedgeAmount && nowAccs[0].Balance * 0.8 / priceBuy > minHedgeAmount) {
                amount = Math.min(amount, nowAccs[1].Stocks, nowAccs[0].Balance * 0.8 / priceBuy, maxHedgeAmount)
                Log("TriggerB->A:", depthB.Bids[0].Price - depthA.Asks[0].Price, priceBuy, priceSell, amount, nowAccs[0].Balance * 0.8 / priceBuy, nowAccs[1].Stocks)  // Prompt Information
                hedge(exA, exB, priceBuy, priceSell, amount)
                cancelAll()
                lastKeepBalanceTS = 0
                isTrade = true 
            }            
        }
        
        if (ts - lastKeepBalanceTS > keepBalanceCyc * 1000) {
            nowAccs = _C(updateAccs, exchanges)
            var isBalance = keepBalance(initAccs, nowAccs, [depthA, depthB])
            cancelAll()
            if (isBalance) {
                lastKeepBalanceTS = ts
                if (isTrade) {
                    var nowBalance = _.reduce(nowAccs, function(sumBalance, acc) {return sumBalance + acc.Balance}, 0)
                    var initBalance = _.reduce(initAccs, function(sumBalance, acc) {return sumBalance + acc.Balance}, 0)
                    LogProfit(nowBalance - initBalance, nowBalance, initBalance, nowAccs)
                    isTrade = false 
                }                
            }

            $.PlotLine("A2B", depthA.Bids[0].Price - depthB.Asks[0].Price)
            $.PlotLine("B2A", depthB.Bids[0].Price - depthA.Asks[0].Price)            
        }
        
        // Interaction
        var cmd = GetCommand()
        if (cmd) {
            Log("Command received:", cmd)
            var arr = cmd.split(":")
            if (arr[0] == "A2B") {
                Log("ModifyA2BParameters of,", diffAsPercentage ? "Parameter is the spread percentage" : "The parameter is the price difference:", arr[1])
                if (diffAsPercentage) {
                    hedgeDiffPercentageB2A = parseFloat(arr[1])
                } else {
                    hedgeDiffPriceA2B = parseFloat(arr[1])
                }
            } else if (arr[0] == "B2A") {                
                Log("ModifyB2AParameters of,", diffAsPercentage ? "Parameter is the spread percentage" : "The parameter is the price difference:", arr[1])
                if (diffAsPercentage) {
                    hedgeDiffPercentageA2B = parseFloat(arr[1])
                } else {
                    hedgeDiffPriceB2A = parseFloat(arr[1])
                }
            }
        }

        var tbl = {
            "type" : "table", 
            "title" : "Data", 
            "cols" : ["Exchange", "coin", "frozen coin", "priced coin", "frozen priced coin", "trigger spread", "current spread"], 
            "rows" : [], 
        }
        tbl.rows.push(["A:" + exA.GetName(), nowAccs[0].Stocks, nowAccs[0].FrozenStocks, nowAccs[0].Balance, nowAccs[0].FrozenBalance, "A->B:" + targetDiffPriceA2B, "A->B:" + (depthA.Bids[0].Price - depthB.Asks[0].Price)])
        tbl.rows.push(["B:" + exB.GetName(), nowAccs[1].Stocks, nowAccs[1].FrozenStocks, nowAccs[1].Balance, nowAccs[1].FrozenBalance, "B->A:" + targetDiffPriceB2A, "B->A:" + (depthB.Bids[0].Price - depthA.Asks[0].Price)])

        LogStatus(_D(), "\n", "`" + JSON.stringify(tbl) + "`")
        Sleep(1000)
    }
}

Published by 发明者量化-小小梦 on the FMZ strategy square · last modified 2021-08-29. Collected in the Quant Trading Vault. Unvalidated research: re-test with your own data and costs before trusting it.

Build this with AgenKit

A spec is not a trading system. Paste this into your coding agent and AgenKit runs a specialist team — architecture, test-first build, risk gates, review — with paper trading on by default.

in Claude Code, Cursor, Codex & 4 more
/agenkit build a production-grade trading bot with a backtest engine, risk controls and paper trading by default from strategies/Spot-Hedging-Strategy-with-Different-Quote-Currencies-Ver11.md
  1. npx agenkit install engineering-kit in your project
  2. Paste the command into your agent and approve each phase
  3. Get tests, backtest, risk controls and a paper-trading switch

Understand it first

Before you run this, read the lesson behind the idea and how to backtest it honestly. Specs like this one are the survivors of an unknown number of trials; deflate their results accordingly.

Related specs

esc

Try , , or .

↑↓ move↵ open