Cross-period Arbitrage Strategy of Digital Currency Based on Bollinger Band
Tags: bollinger, arbitrage, trading, fmzquant
Published: 2024-05-09 05:19:38

### I. Summary
George Soros put forward an important proposition in "The Alchemy of Finance" written in 1987: I believe the market prices are always wrong in the sense that they present a Biased view of the future. The market efficiency hypothesis is only a theoretical hypothesis. In fact, market participants are not always rational, and at each time point, participants cannot completely obtain and objectively interpret all information. Moreover, even if it is the same information, everyone's feedback is different. That is to say, the price itself already contains the wrong expectations of market participants, so in essence, the market price is always wrong. This may be the profit source of arbitrageurs.

### II. Strategic principles
According to the above principles, we can know that in an ineffective futures market, the reason why the market impact on delivery contracts in different periods is not always synchronous, and the pricing is not completely effective. Then, based on the delivery contract price of the same transaction object in different periods, if there is a large price difference between the two prices, we can buy and sell futures contracts in different periods at the same time for cross-period arbitrage. Like commodity futures, digital currency also has a cross-period arbitrage contract portfolio. For example, in the OKEX exchange, there are: ETC current week, ETC next week, ETC quarter.
For example, suppose that the price difference between the current week of ETC and the quarter of ETC remains around 5 for a long time. If the price difference reaches 7 one day, we expect that the price difference will return to 5 in the future. Then we can sell ETC that week and buy ETC quarter at the same time to go short the price difference, vice versa. Although this price difference exists, there are many uncertainties in manual arbitrage due to time-consuming manual operations, poor accuracy and the impact of price changes. The charm of quantitative arbitrage lies in capturing arbitrage opportunities through quantitative models and formulating arbitrage trading strategies, as well as placing trading orders automatically to exchanges through programmed algorithms, so as to capture opportunities quickly and accurately and make profits efficiently and stably.

### III. Strategy logic
This article will teach you how to use the FMZ Quant Trading Platform and the ETC futures contract in the OKEX exchange to demonstrate how to capture the instantaneous arbitrage opportunities, seize the profits that can be seen every time, and hedge the risks that may be encountered in the digital currency trading with a simple arbitrage strategy.

#### Create a cross-period arbitrage strategy for digital currency
Difficulty: Normal

#### Strategy environment
- Transaction object: Ether Classic (ETC)
- Spread data: ETC current week - ETC quarter (omit cointegration test)
- Transaction period: 5 minutes
- Position matching: 1:1
- Transaction type: cross period of the same type

#### Strategy logic
- Conditions for opening positions with going long the price difference: if the current account has no positions and the price difference is less than the lower bound of the boll, then go long the price difference. That is, buy opening positions ETC for the week, sell opening positions ETC for the quarter.
- Conditions for opening positions with going short the price difference: if there is no position in the current account, and the price difference is greater than the upper bound of the boll, then go short the price difference. That is, sell opening positions ETC for the week, buy opening positions ETC for the quarter.
- Conditions for closing positions with going long the price difference: if the current account holds going long orders of ETC in the current week and holds going short orders of ETC quarter, and the price difference is greater than the middle bound of the boll, then close long the price difference. That is, sell closing positions ETC for the week, buy closing positions ETC for the quarter.
- Conditions for closing positions with going short the price difference: if the current account holds going short orders of ETC in the current week, and holds going long orders of ETC quarter, and the price difference is less than the middle bound of the boll, then close short the price difference. That is, buy closing positions ETC for the week, sell closing positions ETC for the quarter.

### IV. Write a strategy framework
The above is a simple logic description of the cross-period arbitrage strategy of digital currency. So how to implement our ideas in the program? We try to build the framework on the FMZ Quant Trading Platform.
```
function Data() {}  // Basic data function
Data.prototype.mp = function () {}  // Position function
Data.prototype.boll = function () {}  // Indicator function
Data.prototype.trade = function () {}  // Order placement function
Data.prototype.cancelOrders = function () {}  // Order withdrawal function
Data.prototype.isEven = function () {}  // Processing single contract function
Data.prototype.drawingChart = function () {}  // Drawing function

// Trading conditions
function onTick() {
    var data = new Data(tradeTypeA, tradeTypeB);  // Create a basic data object
    var accountStocks = data.accountData.Stocks;  // Account balance
    var boll = data.boll(dataLength, timeCycle);  // Calculate the technical indicators of boll
    data.trade();  // Calculate trading conditions to place an order
    data.cancelOrders();  // Cancel orders
    data.drawingChart(boll);  // drawing
    data.isEven();  // Processing of holding individual contract
}

//Entry function
function main() {
    while (true) {  // Enter the polling mode
        onTick();  // Execute onTick function
        Sleep(500);  // Sleep for 0.5 seconds
    }
}
```
### V. Writing strategy
The strategy framework can be easily set up according to the strategy idea and transaction process. The whole strategy can be simplified into three steps:
Pre-processing before transaction.
Get and calculate data.
Place an order and deal with it later.
Next, we need to fill in the necessary detail code in the strategy framework according to the actual transaction process and transaction details.

Pre-processing before transaction
Step 1: Declare the necessary global variables in the global scope.
```
//Declare a chart object for the configuration chart
var chart = { }

//Call Chart function and initialize the chart
var ObjChart = Chart ( chart )

//Declare an empty array to store price difference series
var bars = [ ]

//Declare a record history data timestamp variable
var oldTime = 0
```
Step 2: Configure the external parameters of the strategy.
```
// parameters
var tradeTypeA = "this_week"; // Arbitrage A Contract
var tradeTypeB = "quarter"; // Arbitrage B Contract
var dataLength = 10; //Indicator period length
var timeCycle = 1; // K-line period
var name = "ETC"; // Currencies
var unit = 1; // Order quantity
```
Step 3: Define the data processing function
Basic data function: Data()
Create a constructor, Data, and define its internal properties. Including: account data, position data, K-line data timestamp, buy/sell price of arbitrage A/B contract, and positive/negative arbitrage price difference.
```
// Basic data
function Data(tradeTypeA, tradeTypeB) { // Pass in arbitrage A contract and arbitrage B contract
    this.accountData = _C(exchange.GetAccount); // Get account information
    this.positionData = _C(exchange.GetPosition); // Get position information
    var recordsData = _C(exchange.GetRecords); // Get K-line data
    exchange.SetContractType(tradeTypeA); // Subscription arbitrage A contract
    var depthDataA = _C(exchange.GetDepth); // Depth data of arbitrage A contract
    exchange.SetContractType(tradeTypeB); // Subscription arbitrage B contract
    var depthDataB = _C(exchange.GetDepth); // Depth data of arbitrage B contract
    this.time = recordsData[recordsData.length - 1].Time; // Time of obtaining the latest data
    this.askA = depthDataA.Asks[0].Price; // Sell one price of Arbitrage A contract
    this.bidA = depthDataA.Bids[0].Price; // Buy one price of Arbitrage A contract
    this.askB = depthDataB.Asks[0].Price; // Sell one price of Arbitrage B contract
    this.bidB = depthDataB.Bids[0].Price; // Buy one price of Arbitrage B contract
    // Positive arbitrage price differences (Sell one price of contract A - Buy one price of contract B)
    this.basb = depthDataA.Asks[0].Price - depthDataB.Bids[0].Price;
    // Negative arbitrage price differences (Buy one price of contract A - Sell one price of contract B)
    this.sabb = depthDataA.Bids[0].Price - depthDataB.Asks[0].Price;
}
```
Get the position function: mp ( )
Traverse the entire position array and return the position quantity of the specified contract and direction. If not, return false
```
// Get positions
Data.prototype.mp = function (tradeType, type) {
    var positionData = this.positionData; // Get position information
    for (var i = 0; i < positionData.length; i++) {
        if (positionData[i].ContractType == tradeType) {
            if (positionData[i].Type == type) {
                if (positionData[i].Amount > 0) {
                    return positionData[i].Amount;
                }
            }
        }
    }
    return false;
}
```
K-line and indicator function: boll()
A new K-line sequence is synthesized according to the positive arbitrage/negative arbitrage price difference data. The data of upper track, middle track and lower track calculated by the boll indicator are returned.
```
// Synthesis of new K-line data and boll indicator data
Data.prototype.boll = function (num, timeCycle) {
    var self = {}; // Temporary objects
    // Median value of positive arbitrage price difference and negative arbitrage price difference
    self.Close = (this.basb + this.sabb) / 2;
    if (this.timeA == this.timeB) {
        self.Time = this.time;
    } // Compare two depth data timestamps
    if (this.time - oldTime > timeCycle * 60000) {
        bars.push(self);
        oldTime = this.time;
    } // Pass in the price difference data object into the K-line array according to the specified time period
    if (bars.length > num * 2) {
        bars.shift(); // Control the length of the K-line array
    } else {
        return;
    }
    var boll = TA.BOLL(bars, num, 2); // Call the boll indicator in the talib library
    return {
        up: boll[0][boll[0].length - 1], // boll indicator upper track
        middle: boll[1][boll[1].length - 1], // boll indicator middle track
        down: boll[2][boll[2].length - 1] // boll indicator down track
    } // Return a processed boll indicator data
}
```
Order function: trade()
Pass in the order contract name and order type, then place the order with consideration, and return the result after placing the order. Since it is necessary to place two orders in different directions at the same time, the buy/sell one price is converted within the function according to the contract name of the order.
```
// place the order
Data.prototype.trade = function (tradeType, type) {
    exchange.SetContractType(tradeType); // Resubscribe to a contract before placing an order
    var askPrice, bidPrice;
    if (tradeType == tradeTypeA) { // If the order is placed in contract A
        askPrice = this.askA; // set askPrice
        bidPrice = this.bidA; // set bidPrice
    } else if (tradeType == tradeTypeB) { // If the order is placed in contract B
        askPrice = this.askB; // set askPrice
        bidPrice = this.bidB; // set bidPrice
    }
    switch (type) { // Match order placement mode
        case "buy":
            exchange.SetDirection(type); // Set order placement mode
            return exchange.Buy(askPrice, unit);
        case "sell":
            exchange.SetDirection(type); // Set order placement mode
            return exchange.Sell(bidPrice, unit);
        case "closebuy":
            exchange.SetDirection(type); // Set order placement mode
            return exchange.Sell(bidPrice, unit);
        case "closesell":
            exchange.SetDirection(type); // Set order placement mode
            return exchange.Buy(askPrice, unit);
        default:
            return false;
    }
}
```
Cancel Order Function: cancelOrders()
Get an array of all outstanding orders and cancel them one by one. In addition, false is returned if there is an unfilled order, and true is returned if there is no unfilled order.
```
// Cancel order
Data.prototype.cancelOrders = function () {
    Sleep(500); // Delay before cancellation, because some exchanges, you know what I mean
    var orders = _C(exchange.GetOrders); // Get an array of unfilled orders
    if (orders.length > 0) { // If there are unfilled orders
        for (var i = 0; i < orders.length; i++) { //Iterate through the array of unfilled orders
            exchange.CancelOrder(orders[i].Id); //Cancel unfilled orders one by one
            Sleep(500); //Delay 0.5 seconds
        }
        return false; // Return false if an unfilled order is cancelled
    }
    return true; // Return true if there are no unfilled orders
}
```
Handle holding a single contract: isEven()
In the case of a single leg in the arbitrage transaction, we will simply close all positions. Of course, it can also be changed to the tracking method.
```
// Handle holding a single contract
Data.prototype.isEven = function () {
    var positionData = this.positionData; // Get position information
    var type = null; // Switch position direction
    // If the remaining 2 of the position array length is not equal to 0 or the position array length is not equal to 2
    if (positionData.length % 2 != 0 || positionData.length != 2) {
        for (var i = 0; i < positionData.length; i++) { // Iterate through the position array
            if (positionData[i].Type == 0) { // If it is a long order
                type = 10; // Set order parameters
            } else if (positionData[i].Type == 1) { // If it is a short order
                type = -10; // Set order parameters
            }
            // Close all positions
            this.trade(positionData[i].ContractType, type, positionData[i].Amount);
        }
    }
}
```
Drawing function: drawingChart ( )
Call ObjChart Add () method, draw the necessary market data and indicator data in the chart: upper track, middle track, lower track, positive/negative arbitrage price difference.
```
// Drawing
Data.prototype.drawingChart = function (boll) {
    var nowTime = new Date().getTime();
    ObjChart.add([0, [nowTime, boll.up]]);
    ObjChart.add([1, [nowTime, boll.middle]]);
    ObjChart.add([2, [nowTime, boll.down]]);
    ObjChart.add([3, [nowTime, this.basb]]);
    ObjChart.add([4, [nowTime, this.sabb]]);
    ObjChart.update(chart);
}
```
Step 4: In the entry function main(), execute the pre-transaction pre-processing code, which will only run once after the program is started, including:

- SetErrorFilter( ) to filter the unimportant information in the console
- exchange.IO ( https://www.squadhelp.com/name/exchange.io?lp=d ) ( )  to set the digital currency to be traded
- ObjChart reset ( ) to clear the previous chart drawn before starting the program
- LogProfitReset( ) to clear the status bar information before starting the program
```
//entry function
function main() {
    // Filter the unimportant information in the console
    SetErrorFilter("429|GetRecords:|GetOrders:|GetDepth:|GetAccount|:Buy|Sell|timeout|Futures_OP");
    exchange.IO("currency", name + '_USDT'); //Set the digital currency to be traded
    ObjChart.reset(); // Clear the previous chart drawn before starting the program
    LogProfitReset(); // Clear the status bar information before starting the program
}
```
After the above pre-transaction pre-processing is defined, the next step is to enter the polling mode and execute the onTick() function repeatedly. It also sets the sleep time for Sleep () polling, because the API of some digital currency exchanges has built-in access limit for a certain period of time.
```
//entry function
function main() {
    // Filter the unimportant information in the console
    SetErrorFilter("429|GetRecords:|GetOrders:|GetDepth:|GetAccount|:Buy|Sell|timeout|Futures_OP");
    exchange.IO("currency", name + '_USDT'); //Set the digital currency to be traded
    ObjChart.reset(); //Clear the previous chart drawn before starting the program
    LogProfitReset(); //Clear the status bar information before starting the program
    while (true) { // Enter the polling mode
        onTick(); // Execute onTick function
        Sleep(500); // Sleep for 0.5 seconds
    }
}
```
Get and calculate data
Step 1: Obtain basic data object, account balance, and boll indicator data for use in the trading logic.
```
// Trading conditions
function onTick() {
    var data = new Data(tradeTypeA, tradeTypeB); // Create a basic data object
    var accountStocks = data.accountData.Stocks; // Account balance
    var boll = data.boll(dataLength, timeCycle); // Get boll indicator data
    if (!boll) return; // Return if there is no boll data
}
```
Place an order and handle the follow-up
Step 1: Execute the buying and selling operation according to the above strategic logic. First, judge whether the price and indicator conditions are valid, then judge whether the position conditions are valid, and finally execute the trade () order function
```
// Trading conditions
function onTick() {
    var data = new Data(tradeTypeA, tradeTypeB); // Create a basic data object
    var accountStocks = data.accountData.Stocks; // Account balance
    var boll = data.boll(dataLength, timeCycle); // Get boll indicator data
    if (!boll) return; // Return if there is no boll data
    // Explanation of the price difference
    // basb = (Sell one price of contract A - Buy one price of contract B)
    // sabb = (Buy one price of contract A - Sell one price of contract B)
    if (data.sabb > boll.middle && data.sabb < boll.up) { // If sabb is higher than the middle track
        if (data.mp(tradeTypeA, 0)) { // Check whether contract A has long orders before placing an order
            data.trade(tradeTypeA, "closebuy"); // Contract A closes long position
        }
        if (data.mp(tradeTypeB, 1)) { // Check whether contract B has short orders before placing an order
            data.trade(tradeTypeB, "closesell"); // Contract B closes short position
        }
    } else if (data.basb < boll.middle && data.basb > boll.down) { // If basb is lower than the middle track
        if (data.mp(tradeTypeA, 1)) { // Check whether contract A has short orders before placing an order
            data.trade(tradeTypeA, "closesell"); // Contract A closes short position
        }
        if (data.mp(tradeTypeB, 0)) { // Check whether contract B has long orders before placing an order
            data.trade(tradeTypeB, "closebuy"); // Contract B closes long position
        }
    }
    if (accountStocks * Math.max(data.askA, data.askB) > 1) { // If there is balance in the account
        if (data.basb < boll.down) { // If basb price difference is lower than the down track
            if (!data.mp(tradeTypeA, 0)) { // Check whether contract A has long orders before placing an order
                data.trade(tradeTypeA, "buy"); // Contract A opens long position
            }
            if (!data.mp(tradeTypeB, 1)) { // Check whether contract B has short orders before placing an order
                data.trade(tradeTypeB, "sell"); // Contract B opens short position
            }
        } else if (data.sabb > boll.up) { // If sabb price difference is higher than the upper track
            if (!data.mp(tradeTypeA, 1)) { // Check whether contract A has short orders before placing an order
                data.trade(tradeTypeA, "sell"); // Contract A opens short position
            }
            if (!data.mp(tradeTypeB, 0)) { // Check whether contract B has long orders before placing an order
                data.trade(tradeTypeB, "buy"); // Contract B opens long position
            }
        }
    }
}
```
Step 2: After the order is placed, it is necessary to deal with the abnormal situations such as the unsettled order and the holding of a single contract. And draw the chart.
```
// Trading conditions
function onTick() {
    var data = new Data(tradeTypeA, tradeTypeB); // Create a basic data object
    var accountStocks = data.accountData.Stocks; // Account balance
    var boll = data.boll(dataLength, timeCycle); // Get boll indicator data
    if (!boll) return; // Return if there is no boll data
    // Explanation of the price difference
    //basb = (Sell one price of contract A - Buy one price of contract B)
    // sabb = (Buy one price of contract A - Sell one price of contract B)
    if (data.sabb > boll.middle && data.sabb < boll.up) { // If sabb is higher than the middle track
        if (data.mp(tradeTypeA, 0)) { // Check whether contract A has long orders before placing an order
            data.trade(tradeTypeA, "closebuy"); // Contract A closes long position
        }
        if (data.mp(tradeTypeB, 1)) { // Check whether contract B has short orders before placing an order
            data.trade(tradeTypeB, "closesell"); // Contract B closes short position
        }
    } else if (data.basb < boll.middle && data.basb > boll.down) { // If basb is lower than the middle track
        if (data.mp(tradeTypeA, 1)) { // Check whether contract A has short orders before placing an order
            data.trade(tradeTypeA, "closesell"); // Contract A closes short position
        }
        if (data.mp(tradeTypeB, 0)) { // Check whether contract B has long orders before placing an order
            data.trade(tradeTypeB, "closebuy"); // Contract B closes long position
        }
    }
    if (accountStocks * Math.max(data.askA, data.askB) > 1) { // If there is balance in the account
        if (data.basb < boll.down) { // If basb price difference is lower than the down track
            if (!data.mp(tradeTypeA, 0)) { // Check whether contract A has long orders before placing an order
                data.trade(tradeTypeA, "buy"); // Contract A opens long position
            }
            if (!data.mp(tradeTypeB, 1)) { // Check whether contract B has short orders before placing an order
                data.trade(tradeTypeB, "sell"); // Contract B opens short position
            }
        } else if (data.sabb > boll.up) { // If sabb price difference is higher than the upper track
            if (!data.mp(tradeTypeA, 1)) { // Check whether contract A has short orders before placing an order
                data.trade(tradeTypeA, "sell"); // Contract A opens short position
            }
            if (!data.mp(tradeTypeB, 0)) { // Check whether contract B has long orders before placing an order
                data.trade(tradeTypeB, "buy"); // Contract B opens long position
            }
        }
    }
    data.cancelOrders(); // cancel orders
    data.drawingChart(boll); // drawing
    data.isEven(); // Handle holding individual contracts
}
```
### VI. Complete strategy
As above, we have created a simple cross-period arbitrage strategy of digital currency completely through more than 200 lines code. The complete code is as follows:
```
// Global variable
// Declare a chart object for the configuration chart
var chart = {
    __isStock: true,
    tooltip: {
        xDateFormat: '%Y-%m-%d %H:%M:%S, %A'
    },
    title: {
        text: 'transaction profit and loss curve (detailed)'
    },
    rangeSelector: {
        buttons: [{
            type: 'hour',
            count: 1,
            text: '1h'
        }, {
            type: 'hour',
            count: 2,
            text: '3h'
        }, {
            type: 'hour',
            count: 8,
            text: '8h'
        }, {
            type: 'all',
            text: 'All'
        }],
        selected: 0,
        inputEnabled: false
    },
    xAxis: {
        type: 'datetime'
    },
    yAxis: {
        title: {
            text: 'price difference'
        },
        opposite: false,
    },
    series: [{
        name: "upper track",
        id: "line1,up",
        data: []
    }, {
        name: "middle track",
        id: "line2,middle",
        data: []
    }, {
        name: "down track",
        id: "line3,down",
        data: []
    }, {
        name: "basb",
        id: "line4,basb",
        data: []
    }, {
        name: "sabb",
        id: "line5,sabb",
        data: []
    }]
};
var ObjChart = Chart(chart); // Drawing object
var bars = []; // Storage price difference series
var oldTime = 0; // Record historical data timestamp

// parameters
var tradeTypeA = "this_week"; // Arbitrage A contract
var tradeTypeB = "quarter"; // Arbitrage B contract
var dataLength = 10; //Indicator period length
var timeCycle = 1; // K-line period
var name = "ETC"; // Currencies
var unit = 1; // Order quantity

// basic data
function Data(tradeTypeA, tradeTypeB) { // Pass in arbitrage A contract and arbitrage B contract
    this.accountData = _C(exchange.GetAccount); // Get account information
    this.positionData = _C(exchange.GetPosition); // Get position information
    var recordsData = _C(exchange.GetRecords); //Get K-line data
    exchange.SetContractType(tradeTypeA); // Subscribe to arbitrage A contract
    var depthDataA = _C(exchange.GetDepth); // Arbitrage A contract depth data
    exchange.SetContractType(tradeTypeB); // Subscribe to arbitrage B contract
    var depthDataB = _C(exchange.GetDepth); // Arbitrage B contract depth data
    this.time = recordsData[recordsData.length - 1].Time; // Time to get the latest data
    this.askA = depthDataA.Asks[0].Price; // Sell one price of arbitrage A contract
    this.bidA = depthDataA.Bids[0].Price; // Buy one price of arbitrage A contract
    this.askB = depthDataB.Asks[0].Price; // Sell one price of arbitrage B contract
    this.bidB = depthDataB.Bids[0].Price; // Buy one price of arbitrage B contract
    // Positive arbitrage price difference (Sell one price of contract A - Buy one price of contract B)
    this.basb = depthDataA.Asks[0].Price - depthDataB.Bids[0].Price;
    // Negative arbitrage price difference (Buy one price of contract A - Sell one price of contract B)
    this.sabb = depthDataA.Bids[0].Price - depthDataB.Asks[0].Price;
}

// Get position
Data.prototype.mp = function (tradeType, type) {
    var positionData = this.positionData; // Get position information
    for (var i = 0; i < positionData.length; i++) {
        if (positionData[i].ContractType == tradeType) {
            if (positionData[i].Type == type) {
                if (positionData[i].Amount > 0) {
                    return positionData[i].Amount;
                }
            }
        }
    }
    return false;
}

// Synthesis of new K-line data and boll indicator data
Data.prototype.boll = function (num, timeCycle) {
    var self = {}; // Temporary objects
    // Median value of between positive arbitrage price difference and negative arbitrage price difference
    self.Close = (this.basb + this.sabb) / 2;
    if (this.timeA == this.timeB) {
        self.Time = this.time;
    } // Compare two depth data timestamps
    if (this.time - oldTime > timeCycle * 60000) {
        bars.push(self);
        oldTime = this.time;
    } // Pass in the price difference data object into the K-line array according to the specified time period
    if (bars.length > num * 2) {
        bars.shift(); // Control the length of the K-line array
    } else {
        return;
    }
    var boll = TA.BOLL(bars, num, 2); // Call the boll indicator in the talib library
    return {
        up: boll[0][boll[0].length - 1], // boll indicator upper track
        middle: boll[1][boll[1].length - 1], // boll indicator middle track
        down: boll[2][boll[2].length - 1] // boll indicator down track
    } // Return a processed boll indicator data
}

// Place an order
Data.prototype.trade = function (tradeType, type) {
    exchange.SetContractType(tradeType); // Resubscribe to a contract before placing an order
    var askPrice, bidPrice;
    if (tradeType == tradeTypeA) { // If the order is placed in contract A
        askPrice = this.askA; // Set askPrice
        bidPrice = this.bidA; // Set bidPrice
    } else if (tradeType == tradeTypeB) { // If the order is placed in contract B
        askPrice = this.askB; // Set askPrice
        bidPrice = this.bidB; // Set bidPrice
    }
    switch (type) { // Match order placement mode
        case "buy":
            exchange.SetDirection(type); // Set order placement mode
            return exchange.Buy(askPrice, unit);
        case "sell":
            exchange.SetDirection(type); // Set order placement mode
            return exchange.Sell(bidPrice, unit);
        case "closebuy":
            exchange.SetDirection(type); // Set order placement mode
            return exchange.Sell(bidPrice, unit);
        case "closesell":
            exchange.SetDirection(type); // Set order placement mode
            return exchange.Buy(askPrice, unit);
        default:
            return false;
    }
}

// Cancel orders
Data.prototype.cancelOrders = function () {
    Sleep(500); // Delay before cancellation, because some exchanges, you know what I mean
    var orders = _C(exchange.GetOrders); //Get an array of unfilled orders
    if (orders.length > 0) { // If there are unfilled orders
        for (var i = 0; i < orders.length; i++) { //Iterate through the array of unfilled orders
            exchange.CancelOrder(orders[i].Id); //Cancel unfilled orders one by one
            Sleep(500); //Sleep for 0.5 seconds
        }
        return false; // Return false if an unfilled order is cancelled
    }
    return true; //Return true if there are no unfilled orders
}

// Handle holding individual contracts
Data.prototype.isEven = function () {
    var positionData = this.positionData; // Get position information
    var type = null; // Switch position direction
    // If the remaining 2 of the position array length is not equal to 0 or the position array length is not equal to 2
    if (positionData.length % 2 != 0 || positionData.length != 2) {
        for (var i = 0; i < positionData.length; i++) { // Iterate through the position array
            if (positionData[i].Type == 0) { // If it is a long order
                type = 10; // Set order parameters
            } else if (positionData[i].Type == 1) { // If it is a short order
                type = -10; // Set order parameters
            }
            // Close all positions
            this.trade(positionData[i].ContractType, type, positionData[i].Amount);
        }
    }
}

// Drawing
Data.prototype.drawingChart = function (boll) {
    var nowTime = new Date().getTime();
    ObjChart.add([0, [nowTime, boll.up]]);
    ObjChart.add([1, [nowTime, boll.middle]]);
    ObjChart.add([2, [nowTime, boll.down]]);
    ObjChart.add([3, [nowTime, this.basb]]);
    ObjChart.add([4, [nowTime, this.sabb]]);
    ObjChart.update(chart);
}

// Trading conditions
function onTick() {
    var data = new Data(tradeTypeA, tradeTypeB); // Create a basic data object
    var accountStocks = data.accountData.Stocks; // Account balance
    var boll = data.boll(dataLength, timeCycle); // Get boll indicator data
    if (!boll) return; // Return if there is no boll data
    // Explanation of price difference
    // basb = (Sell one price of contract A - Buy one price of contract B)
    // sabb = (Buy one price of contract A - Sell one price of contract B)
    if (data.sabb > boll.middle && data.sabb < boll.up) { // If sabb is higher than the middle track
        if (data.mp(tradeTypeA, 0)) { // Check whether contract A has long orders before placing an order
            data.trade(tradeTypeA, "closebuy"); // Contract A closes long position
        }
        if (data.mp(tradeTypeB, 1)) { // Check whether contract B has short orders before placing an order
            data.trade(tradeTypeB, "closesell"); // Contract B closes short position
        }
    } else if (data.basb < boll.middle && data.basb > boll.down) { // If basb is lower than the middle track
        if (data.mp(tradeTypeA, 1)) { // Check whether contract A has short orders before placing an order
            data.trade(tradeTypeA, "closesell"); // Contract A closes short position
        }
        if (data.mp(tradeTypeB, 0)) { // Check whether contract B has long orders before placing an order
            data.trade(tradeTypeB, "closebuy"); // Contract B closes long position
        }
    }
    if (accountStocks * Math.max(data.askA, data.askB) > 1) { // If there is a balance in the account
        if (data.basb < boll.down) { // If basb price difference is lower than the down track
            if (!data.mp(tradeTypeA, 0)) { // Check whether contract A has long orders before placing an order
                data.trade(tradeTypeA, "buy"); // Contract A opens long position
            }
            if (!data.mp(tradeTypeB, 1)) { // Check whether contract B has short orders before placing an order
                data.trade(tradeTypeB, "sell"); // Contract B opens short position
            }
        } else if (data.sabb > boll.up) { // If sabb price difference is higher than the upper track
            if (!data.mp(tradeTypeA, 1)) { // Check whether contract A has short orders before placing an order
                data.trade(tradeTypeA, "sell"); // Contract A opens short position
            }
            if (!data.mp(tradeTypeB, 0)) { // Check whether contract B has long orders before placing an order
                data.trade(tradeTypeB, "buy"); // Contract B opens long position
            }
        }
    }
    data.cancelOrders(); // Cancel orders
    data.drawingChart(boll); // Drawing
    data.isEven(); // Handle holding individual contracts
}

//Entry function
function main() {
    // Filter unimportant information in the console
    SetErrorFilter("429|GetRecords:|GetOrders:|GetDepth:|GetAccount|:Buy|Sell|timeout|Futures_OP");
    exchange.IO("currency", name + '_USDT'); //Set the digital currency to be traded
    ObjChart.reset(); //Clear the previous chart drawn before starting the program
    LogProfitReset(); //Clear the status bar information before starting the program
    while (true) { // Enter polling mode
        onTick(); // Execute the onTick function
        Sleep(500); // Sleep for 0.5 seconds
    }
}
```
Strategy address:
https://www.fmz.com/strategy/104964

### VII. Summary
The strategy in this article is just an example. The real bot is not simple, but you can follow the example and use your own wild imagination It should be reminded that, based on my limited experience, in the current digital currency market, almost all pure futures-futures arbitrage strategies are not worth running, whether it is risk-free triangular arbitrage or cross-market arbitrage.
The reason is that no matter which digital currency exchange futures market, its margin is not legal currency. At pressent, almost all digital currencies have dropped by about 70% since the beginning of the year. That is to say, the strategy is always to earn currencies, but the currency price is decreasing. On the whole, the digital currency market seems to have detached from the blockchain. Like the tulips back then, the price always comes from people's expectations and confidence, and confidence comes from the price...

From: https://blog.mathquant.com/2022/12/02/cross-period-arbitrage-strategy-of-digital-currency-based-on-bollinger-band.html