In the previous article, we implemented a simple hedging strategy together, and then we will learn how to upgrade this strategy. The strategy changes are not big, but the details of the changes need attention. The definitions of some places in the code have changed from the previous ones, which need to be understood.
The need to upgrade this strategy
Switching spot exchange object leverage mode This change is only related to the real bot. Some spot exchanges have spot leverage interfaces, which are also encapsulated on FMZ. For exchange objects that have been directly packaged on FMZ and support spot leverage, the mode can be switched directly.
Add spread chart display Add spread chart display, because it's just drawing the spread line of
A exchange -> B exchange
,B exchange -> A exchange
, and drawing the horizontal line that triggers the spread. We use theline drawing class library
to deal with directly, the advantage is that it is easy to use, here we also learn how to use thetemplate class library
function of FMZ.One-sided hedging function This change can be quite significant, because it is difficult to completely reverse the price difference between the two exchanges during specific hedging transactions. Most of the time the price on one exchange is consistently higher than the price on another exchange. At this time, if our assets have been fully hedged (that is, the coins are all on exchanges with low prices, and the money is on exchanges with high prices). The hedging is stagnant, and it is no longer possible to rely on the fluctuation of the spread to make a profit. At this time, we need to make the strategy so that you can lose a little money to hedge the coins back (let the coins exist on the exchange with high price again), and when the price difference becomes larger again, we can continue to hedge and earn profit.
Interactively modify parameters such as hedging spread lines Add interactive function to the strategy to modify the spread trigger line in real time.
Organize status bar information and display it in a table format Arrange the data that needs to be displayed for easy viewing.
Next, let's implement these designs one by one.
Switch spot exchange object leverage mode
Take Binance spot real bot as an example, switch to spot leveraged mode, use the code exchanges[i].IO
, input the parameter trade_normal
to switch to leverage position by position, and input trade_super_margin
to switch to leverage full position, backtesting is not supported. This is only used in real bot.
Add to the preparation phase at the beginning of the main
function:
// Switch leverage mode
for (var i = 0 ; i < exchanges.length ; i++) { // Traverse and detect all added exchange objects
if (exchanges[i].GetName() == "Binance" && marginType != 0) { //If the exchange object represented by the current i-index is Binance spot, and the parameter marginType of the strategy interface is not the option of "common currency", execute the switch operation
if (marginType == 1) {
Log(exchanges[i].GetName(), "Set to leveraged position-by-position")
exchanges[i].IO("trade_normal")
} else if (marginType == 2) {
Log(exchanges[i].GetName(), "Set to leveraged full position")
exchanges[i].IO("trade_super_margin")
}
}
}
The strategy here only adds the code for switching the coin-to-coin leverage mode of Binance spot, so the switch setting on the strategy parameters is only valid for Binance spot.
Added spread chart display
It is very easy to use the already wrapped drawing template. The template name we use is Line Drawing Library
. It can be obtained by searching directly on the FMZ platform strategy square.
Or click the link directly: https://www.fmz.com/strategy/27293 to jump to the copy page for this template.
Click the button to copy this template class library to your own strategy library.
Then you can check the template class library to be used in the template column on the strategy editing page. Save the strategy after checking it, and the strategy will refer to this template. This is just a brief description of the use of the template class library. This strategy has already referenced this template, so there is no need to repeat the operation. When you copy this strategy in the strategy square, you can see that Line Drawing Library
has been referenced in the template bar on the strategy editing page.
We will mainly learn how to use the functions of the Line Drawing Library
to draw a chart.
We plan to draw the spread of A->B
, the spread of B->A
, and the trigger line of the spread. We need to draw two curves (current A to B, B to A spread), two horizontal lines (trigger spread line), as shown in the figure above.
Because we want to design unilateral hedging, the trigger lines of A->B
and B->A
are different. We cannot use the design in the previous article. In the previous article:
var targetDiffPrice = hedgeDiffPrice
if (diffAsPercentage) {
targetDiffPrice = (depthA.Bids[0].Price + depthB.Asks[0].Price + depthB.Bids[0].Price + depthA.Asks[0].Price) / 4 * hedgeDiffPercentage
}
There is only one trigger spread targetDiffPrice
. So here we have to transform the code, transform the parameters first.
Then modify the code:
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
targetDiffPriceB2A = (depthA.Bids[0].Price + depthB.Asks[0].Price + depthB.Bids[0].Price + depthA.Asks[0].Price) / 4 * hedgeDiffPercentageB2A
}
In this way, the difference trigger line has changed from the previous targetDiffPrice
to two targetDiffPriceA2B
, targetDiffPriceB2A
. The next step is to draw this data on the chart by using the draw line function of the line drawing library.
// drawing
$.PlotHLine(targetDiffPriceA2B, "A->B") // The first parameter of this function is the value of the horizontal line in the Y-axis direction, and the second parameter is the display text
$.PlotHLine(targetDiffPriceB2A, "B->A")
When the strategy is running, there is a chart like this.
Next draw the real-time spread curve, to avoid overdrawing the line. Put the code that draws the curve of the real-time spread data in the balance check.
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) // Draw real-time spread curves
$.PlotLine("B2A", depthB.Bids[0].Price - depthA.Asks[0].Price) // The first parameter is the name of the curve, and the second parameter is the value of the curve at the current moment, that is, the value in the Y-axis direction at the current moment
}
In this way, the drawing code is only 4 lines, allowing the strategy to have a graph displayed at runtime.
One-sided hedging function
As mentioned above, the spread trigger line has been modified into two, controlling the hedging trigger of A->B
and B->A
respectively. In this way, the previous order price algorithm cannot be used, and the method of adding slip price to the market price is used instead.
if (depthA.Bids[0].Price - depthB.Asks[0].Price > targetDiffPriceA2B && Math.min(depthA.Bids[0].Amount, depthB.Asks[0].Amount) >= minHedgeAmount) { // A -> B market conditions are met
var priceSell = depthA.Bids[0].Price - slidePrice
var priceBuy = depthB.Asks[0].Price + slidePrice
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("trigger A->B:", depthA.Bids[0].Price - depthB.Asks[0].Price, priceBuy, priceSell, amount, nowAccs[1].Balance * 0.8 / priceSell, nowAccs[0].Stocks) // Tips
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 market conditions are met
var priceBuy = depthA.Asks[0].Price + slidePrice
var priceSell = depthB.Bids[0].Price - slidePrice
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("trigger B->A:", depthB.Bids[0].Price - depthA.Asks[0].Price, priceBuy, priceSell, amount, nowAccs[0].Balance * 0.8 / priceBuy, nowAccs[1].Stocks) //Tips
hedge(exA, exB, priceBuy, priceSell, amount)
cancelAll()
lastKeepBalanceTS = 0
isTrade = true
}
}
Since the buying and selling prices are separated into two data, the hedging function hedge
also needs to be modified.
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()
}
There are also some minor adjustments based on these changes, which will not be repeated here. You can look at the code for details.
Interactively modify parameters such as hedging spread lines
Add interaction to the strategy, so that the strategy can modify the spread trigger line in real time. This is the design requirement of a semi-automatic strategy, which is also implemented here as a teaching demonstration. The strategy interaction design is also very simple. First, add interaction controls to the strategy on the strategy editing page.
Added two controls, one called A2B and one called B2A. After entering a value in the control input box, click the button to the right of the input box. An instruction will be sent to the strategy immediately, for example: enter the value 123
in the input box, click the A2B
button, and an instruction will be sent to the strategy immediately.
A2B:123
Design interactive detection and processing code in the strategy code.
// interact
var cmd = GetCommand() // Every time the loop is executed here, it checks whether there is an interactive command, and returns to an empty string if not.
if (cmd) { // An interactive command was detected, such as A2B:123
Log("command received:", cmd)
var arr = cmd.split(":") // Split out the interactive control name and the value in the input box, arr[0] is A2B, arr[1] is 123
if (arr[0] == "A2B") { // Determine whether the triggered interactive control is A2B
Log("Modify the parameters of A2B, ", diffAsPercentage ? "The parameter is the difference percentage" : "The parameter is the difference:", arr[1])
if (diffAsPercentage) {
hedgeDiffPercentageB2A = parseFloat(arr[1]) // Modify the trigger spread line
} else {
hedgeDiffPriceA2B = parseFloat(arr[1]) // Modify the trigger spread line
}
} else if (arr[0] == "B2A") { // Triggered control detected is B2A
Log("Modify the parameters of B2A, ", diffAsPercentage ? "The parameter is the difference percentage" : "The parameter is the difference:", arr[1])
if (diffAsPercentage) {
hedgeDiffPercentageA2B = parseFloat(arr[1])
} else {
hedgeDiffPriceB2A = parseFloat(arr[1])
}
}
}
Organize status bar information and display it in a table format
Make the status bar data display more organized and easy to observe.
var tbl = {
"type" : "table",
"title" : "data",
"cols" : ["exchange", "coin", "freeze coin", "denominated currency", "freeze denominated currency", "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) + "`")
Backtesting
Backtesting is only a test strategy, a preliminary detection function, and many bugs can be tested out in the backtesting stage actually. It is not necessary to pay too much attention to the backtesting results. The final strategy still needs to be tested in the real environment.
Strategy source code: https://www.fmz.com/strategy/302834
From: https://blog.mathquant.com/2022/07/25/cryptocurrency-spot-hedging-strategy-design-2.html