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.
A exchange -> B exchange
, B exchange -> A exchange
, and drawing the horizontal line that triggers the spread. We use the line drawing class library
to deal with directly, the advantage is that it is easy to use, here we also learn how to use the template class library
function of FMZ.Next, let’s implement these designs one by one.
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.
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.
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.
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])
}
}
}
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 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