For beginners, hedging strategies are a very good practice strategy. This article implements a simple but practical digital currency spot hedging strategy, hopefully giving beginners some design experience.
First of all, it should be clear that the strategy that we are going to design is a digital currency spot hedging strategy, we have designed the simplest hedge, only the higher-priced exchanges sell between the two spot exchanges, the lower-priced exchanges buy and thus earn the difference. When the higher-priced exchanges are all bills (because the higher-priced coins are all sold), the lower-priced exchanges are all coins (because the lower-priced exchanges are all coins) cannot be hedged.
The price, quantity, and precision of the order are limited, and there is a minimum order limit. In addition to the minimum order limit, the strategy outside of the limit also considers the maximum order size of the hedge, and there will not be enough order volume if the order size is too large. It is also necessary to consider how to convert the exchange rate if the two exchange rates are different.
Based on these considerations, the strategy needs to design several parameters:
hedgeDiffPrice
, when the spread exceeds this value, trigger a hedge operation.minHedgeAmount
This is the lowest amount of money that can be hedged.maxHedgeAmount
The maximum amount of the hedge (the number of coins) is the maximum amount of the hedge.pricePrecisionA
The price precision of the order (in small digits) on the A exchange.amountPrecisionA
, A is the precision of the unit (smaller digit) of the exchange.pricePrecisionB
The price accuracy of the B exchange order (in small digits).amountPrecisionB
This is the lowest single-digit accuracy on the B exchange.rateA
, the exchange rate conversion of the first exchange object added, the default 1 not converted.rateB
, the exchange rate conversion of the second exchange object added, the default 1 is not converted.A hedging strategy requires that the number of coins in both accounts is constant (i.e. no directional position is held, so it is neutral), so there is a balancing logic in the strategy that always detects the balance. When detecting the balance, we avoid having to obtain asset data from both exchanges. We need to write a function to use it.
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
}
If an order is not completed after the current order, we need to cancel it in a timely manner, and we cannot keep the order hanging. This operation needs to be handled both in the balancing module and in the hedging logic, so we also need to design an order full-withdrawal function.
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 that accumulates to a certain number of coins in some depth data, so we need a function like this to handle it.
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
}
And then we need to design and write a specific hedging sub-order operation, which we need to design as a concurrent sub-order:
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, we're going to finish the design of the equilibrium function, which is a little bit more complicated.
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
// 计算币差
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("无法平衡")
} else {
// 平衡下单
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("价格无效", price)
}
}
return false
} else if (!(initAccs.length == nowAccs.length && nowAccs.length == depths.length)) {
Log("错误:", "initAccs.length:", initAccs.length, "nowAccs.length:", nowAccs.length, "depths.length:", depths.length)
return true
} else {
return true
}
}
These functions are designed according to the policy needs, and below you can start designing the main functions of the policy.
The strategy on FMZ is frommain
The function starts executing at.main
In the beginning part of the function, we're going to do some initialization work on the strategy.
Name of the exchange Since many of the strategies are used to access exchange objects, such as obtaining markets, placing orders, etc.; so using a longer name every time can be a problem, the trick 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.
Currency exchange rate, accuracy related design
// 精度,汇率设置
if (rateA != 1) {
// 设置汇率A
exA.SetRate(rateA)
Log("交易所A设置汇率:", rateA, "#FF0000")
}
if (rateB != 1) {
// 设置汇率B
exB.SetRate(rateB)
Log("交易所B设置汇率:", rateB, "#FF0000")
}
exA.SetPrecision(pricePrecisionA, amountPrecisionA)
exB.SetPrecision(pricePrecisionB, amountPrecisionB)
If the exchange rate parameterrateA
、rateB
There's a setting of 1 (the default is 1), which israteA != 1
orrateB != 1
The exchange rate conversion will not be set up, so it will not trigger.
Reset all data
Sometimes a policy needs to be deleted from all logs and blank records when it starts. A policy interface parameter can be designed.isReset
, and then re-design the part of the code initialized in the policy, for example:
if (isReset) { // 当isReset为真时重置数据
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log("重置所有数据", "#FF0000")
}
Restore original account data, update current account data
In order to judge the balance, the strategy requires continuous recording of the initial accounting assets used in comparison to the current ones.nowAccs
This is the variable that records the current account data using the functions that we just designed.updateAccs
Get the account data of the current exchange.initAccs
For the purpose of recording the initial account balance (data such as the number of coins in exchange A and exchange B, the number of coins quoted)initAccs
First use_G()
Function recovery ((_G) The _G function records data permanently and can return data that has been recorded. See API documentation:LinksIf you can't find it, assign and use the current account information._G
The function registers.
For example, the following code:
var nowAccs = _C(updateAccs, exchanges)
var initAccs = _G("initAccs")
if (!initAccs) {
initAccs = nowAccs
_G("initAccs", initAccs)
}
The code in the main loop is the process executed by the strategy logic every round, and the continuous repetition of the execution constitutes the main loop. Let's look at the process executed by the program in the main loop each time.
Get market data to judge the effectiveness of market data
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
}
Here you can see the concurrent functions using the FMZ platform.exchange.Go
I created a call.GetDepth()
Simultaneous objects of the interfacedepthARoutine
、depthBRoutine
│ When creating these two concurrent objects, callGetDepth()
The interface also happened immediately, when both requests for deep data were sent to the exchange in the past.
Then call.depthARoutine
、depthBRoutine
The objectwait()
How to get deep data.
After obtaining the deep data, it is necessary to examine the deep data to determine its effectiveness.continue
The sentence re-executes the main loop.
Use价差值
Are the parameters差价比例
The parameters?
var targetDiffPrice = hedgeDiffPrice
if (diffAsPercentage) {
targetDiffPrice = (depthA.Bids[0].Price + depthB.Asks[0].Price + depthB.Bids[0].Price + depthA.Asks[0].Price) / 4 * hedgeDiffPercentage
}
The parameters we've done are this. The parameters of FMZ can be based on some parameters.DisplayOrHidingSo we can do a parameter to decide whether to use.价格差
Or,差价比例
。
Adding a parameter to the policy interface parameterdiffAsPercentage
The other two parameters that are displayed or hidden based on this parameter are:hedgeDiffPrice@!diffAsPercentage
When,diffAsPercentage
To show this parameter as false.hedgeDiffPercentage@diffAsPercentage
When,diffAsPercentage
This parameter is shown as true.
So, after designing it, we selected the right ones.diffAsPercentage
Parameters, which are used to trigger a hedge in terms of the price difference.diffAsPercentage
The parameter is the price difference as a hedge trigger condition.
Determining the trigger conditions of a hedge
if (depthA.Bids[0].Price - depthB.Asks[0].Price > targetDiffPrice && Math.min(depthA.Bids[0].Amount, depthB.Asks[0].Amount) >= minHedgeAmount) { // A -> B 盘口条件满足
var price = (depthA.Bids[0].Price + depthB.Asks[0].Price) / 2
var amount = Math.min(depthA.Bids[0].Amount, depthB.Asks[0].Amount)
if (nowAccs[0].Stocks > minHedgeAmount && nowAccs[1].Balance / price > minHedgeAmount) {
amount = Math.min(amount, nowAccs[0].Stocks, nowAccs[1].Balance / price, maxHedgeAmount)
Log("触发A->B:", depthA.Bids[0].Price - depthB.Asks[0].Price, price, amount, nowAccs[1].Balance / price, nowAccs[0].Stocks) // 提示信息
hedge(exB, exA, price, amount)
cancelAll()
lastKeepBalanceTS = 0
isTrade = true
}
} else if (depthB.Bids[0].Price - depthA.Asks[0].Price > targetDiffPrice && Math.min(depthB.Bids[0].Amount, depthA.Asks[0].Amount) >= minHedgeAmount) { // B -> A 盘口条件满足
var price = (depthB.Bids[0].Price + depthA.Asks[0].Price) / 2
var amount = Math.min(depthB.Bids[0].Amount, depthA.Asks[0].Amount)
if (nowAccs[1].Stocks > minHedgeAmount && nowAccs[0].Balance / price > minHedgeAmount) {
amount = Math.min(amount, nowAccs[1].Stocks, nowAccs[0].Balance / price, maxHedgeAmount)
Log("触发B->A:", depthB.Bids[0].Price - depthA.Asks[0].Price, price, amount, nowAccs[0].Balance / price, nowAccs[1].Stocks) // 提示信息
hedge(exA, exB, price, amount)
cancelAll()
lastKeepBalanceTS = 0
isTrade = true
}
}
There are several conditions for triggering a hedge:
1, the hedging spread is satisfied first, and is only hedged when the spread of the transaction satisfies the set of the spread parameters.
2, the transactional hedge must satisfy the minimum hedge set in the parameter, because the minimum amount of down payment that different exchanges may limit is different, so the smallest of the two is taken.
3. The assets in the exchange that sold the operation are enough to sell, and the assets in the exchange that bought the operation are enough to buy.
When these conditions are met, the hedging function is executed. Before the main function, we declare a variable in advance.isTrade
To mark whether a hedge has occurred, here if a hedge is triggered, set the variable totrue
And reset the global variables.lastKeepBalanceTS
Set to 0 (lastKeepBalanceTS is used to mark the timestamp of the most recent balance operation, set to 0 will trigger the balance operation immediately) and then undo all pending lists.
Balancing operation
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
}
}
}
You can see that the equilibrium function is executed regularly, but if the hedge is triggered after the operation, the balance function is executed regularly.lastKeepBalanceTS
A balancing operation reset to 0 is triggered immediately. The gain is calculated after the balancing is successful.
Status bar information
LogStatus(_D(), "A->B:", depthA.Bids[0].Price - depthB.Asks[0].Price, " B->A:", depthB.Bids[0].Price - depthA.Asks[0].Price, " targetDiffPrice:", targetDiffPrice, "\n",
"当前A,Stocks:", nowAccs[0].Stocks, "FrozenStocks:", nowAccs[0].FrozenStocks, "Balance:", nowAccs[0].Balance, "FrozenBalance", nowAccs[0].FrozenBalance, "\n",
"当前B,Stocks:", nowAccs[1].Stocks, "FrozenStocks:", nowAccs[1].FrozenStocks, "Balance:", nowAccs[1].Balance, "FrozenBalance", nowAccs[1].FrozenBalance, "\n",
"初始A,Stocks:", initAccs[0].Stocks, "FrozenStocks:", initAccs[0].FrozenStocks, "Balance:", initAccs[0].Balance, "FrozenBalance", initAccs[0].FrozenBalance, "\n",
"初始B,Stocks:", initAccs[1].Stocks, "FrozenStocks:", initAccs[1].FrozenStocks, "Balance:", initAccs[1].Balance, "FrozenBalance", initAccs[1].FrozenBalance)
The status bar is not particularly complex in design, showing the current time, showing the difference from exchange A to exchange B and exchange B to exchange A. It shows the current hedging target difference. It shows asset data for exchange A and asset data for exchange B.
In the parameters, we designed the conversion of exchange rate values parameters, and at the beginning of the strategy,main
We also designed the exchange rate conversion part of the initial operation of the function.SetRate
The exchange rate conversion function needs to be executed first.
This is because this function affects two levels:
BTC_USDT
The price is the same.USDT
In addition, the account can also be used as a currency for account assets.USDT
If I want to convert the value to CNY, set it in the code.exchange.SetRate(6.8)
I'll just take it.exchange
All the data obtained by the functions under this exchange object is converted into CNY.
In exchange for why the coin is priced,SetRate
Function forwardingThe exchange rate of the current currency to the target currency。The full strategy:Hedging strategies for different currencies (Teaching)
Squirrels in the UkraineThat's great.