Hedging strategies are very good practice strategies for beginners in strategy design. This article implements a simple but live cryptocurrency spot hedging strategy, hoping that beginners can learn some design experience.
Design some functions and strategy interface parameters according to the strategy requirements
First of all, it is clear that the strategy to be designed is a cryptocurrency spot hedging strategy. We design the simplest hedging strategy. We sell on the exchange with the higher price only between the two spot exchanges, and buy on the exchange with the lower price to take the difference. When the exchanges with higher prices are all denominated coins (because the coins with higher prices are sold), and the exchanges with lower prices are all coins (the coins with lower prices are bought), it cannot be hedged. At this time, we can only wait for the price reversal to hedge.
When hedging, the price and quantity of the order are limited by the exchange, and there is also a limit on the minimum order quantity. In addition to the minimum limit, strategy in hedging also needs to consider the maximum order volume at one time. If the order volume is too large, there will not be enough order volume. It is also necessary to consider how to convert the exchange rate if the two exchange-denominated coins are different. When hedging, the handling fee and slippage of the order taker are all transaction costs, not as long as there is a price difference can be hedged. Therefore, the hedging price difference also has a trigger value. If it is lower than a certain price difference, the hedging will lose.
Based on these considerations, the strategy needs to be designed with several parameters:
Hedge difference: hedgeDiffPrice, when the difference exceeds this value, the hedging operation is triggered.
Minimum hedge amount:minHedgeAmount, the minimum order amount (coins) that can be hedged.
Maximum hedge amount:maxHedgeAmount, the maximum order amount (coins) for one hedging.
Price precision of A: pricePrecisionA, the order price precision (number of decimal places) placed by Exchange A.
Amount precision of A: amountPrecisionA, the amount precision of the order placed by Exchange A (number of decimal places).
Price precision of B: pricePrecisionB, the order price precision (number of decimal places) placed by Exchange B.
Amount precision of B: amountPrecisionB, the amount precision of the order placed by Exchange B (number of decimal places).
Rate of Exchange A: rateA, the exchange rate conversion of the first added exchange object, the default is 1, not converted.
Rate of Exchange B: rateB, the exchange rate conversion of the second added exchange object, the default is 1, not converted.
The hedging strategy needs to keep the number of coins in the two accounts unchanged (that is, not holding positions in any direction, and maintaining neutrality), so there needs to be a balance logic in the strategy to always detect the balance. When checking balance, it is unavoidable to obtain asset data from two exchanges. We need to write a function to use.
updateAccs
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
}
After placing the order, if there is no completed order, we need to cancel it in time, and the order cannot be kept pending. This operation needs to be processed in both the balance module and the hedging logic, so it is also necessary to design an order full withdrawal function.
cancelAll
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)
}
}
})
}
When balancing the number of coins, we need to find the price accumulated to a certain number of coins in a certain depth data, so we need such a function to handle it.
getDepthPrice
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
}
Then we need to design and write the specific hedging order operation, which needs to be designed to place concurrent orders:
hedge
function hedge(buyEx, sellEx, price, amount) {
var buyRoutine = buyEx.Go("Buy", price, amount)
var sellRoutine = sellEx.Go("Sell", price, amount)
Sleep(500)
buyRoutine.wait()
sellRoutine.wait()
}
Finally, let's complete the design of the balance function, which is slightly complicated.
keepBalance
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 the currency difference
if (Math.abs(diff) > minHedgeAmount && initAccs.length == nowAccs.length && nowAccs.length == depths.length) {
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 > Math.abs(diff)) {
available.push(i)
} else if (price && nowAccs[i].Balance / price > 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 (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("errors:", "initAccs.length:", initAccs.length, "nowAccs.length:", nowAccs.length, "depths.length:", depths.length)
return true
} else {
return true
}
}
After designing these functions according to the strategy requirements, then start to design the main function of the strategy.
Main function design of the strategy
On FMZ platform, the strategy is executed from the main function. At the beginning of the main function, we have to do some initialization work of the strategy.
Exchange object name
Because many operations in the strategy have to use the exchange objects, such as getting market quotations, placing orders and so on. So it would be cumbersome to use a long name every time, the tip is to use a simple name instead, for example:
var exA = exchanges[0]
var exB = exchanges[1]
This makes it easier to write code later.
Exchange rate, precision related design
// precision, exchange rate settings
if (rateA != 1) {
// set exchange rate A
exA.SetRate(rateA)
Log("Exchange A sets the exchange rate:", rateA, "#FF0000")
}
if (rateB != 1) {
// set exchange rate B
exB.SetRate(rateB)
Log("Exchange B sets the exchange rate:", rateB, "#FF0000")
}
exA.SetPrecision(pricePrecisionA, amountPrecisionA)
exB.SetPrecision(pricePrecisionB, amountPrecisionB)
If the exchange rate parameters rateA, rateB are set to 1 (the default is 1), that is, rateA != 1 or rateB != 1 will not trigger, so the exchange rate conversion will not be set.
To be continued...