NO.1
In the book "Financial Alchemy" written by Soros in 1987, an important proposition was put forward: I believe the market prices are always wrong in the sense that they present a biased view of the future.
The market validity hypothesis is only a theoretical assumption. In fact, market participants are not always rational, and at each point of time, participants cannot fully acquire and objectively interpret all information. Even if it is the same information, everyone's feedback is different.

In other words, the price itself already contains the wrong expectations of market participants, so in essence the market price is always wrong. This may be the source of profit for the arbitrageurs.

NO.2
Based on the above principles, we also know that in a non-effective futures market, the market impact of delivery contracts in different periods is not always synchronized, and the pricing is not the reason of completely effective.

Then, based on the price of the delivery contract at different times of the same transaction target, if there is a large spread between the two prices, it is possible to simultaneously trade futures contracts of different periods and carry out intertemporal arbitrage.
Like commodity futures, digital currencies also have an intertemporal arbitrage contract portfolio associated with them. For example, in the OkEX exchange: ETC week, ETC next week, ETC quarter.

For example, suppose the spread between ETC week and ETC quarter is maintained at around 5 for a long time. If the spread reaches 7, we expect the spread to return to 5 at some time in the future. Then you can sell the ETC week and buy the ETC quarter to short the spread. vice versa.

NO.3
Although this spread exists, there are often many uncertainties in manual arbitrage due to the time-consuming, poorly accurate of manual operations and price-changing effects.

Through the quantitative model to capture arbitrage opportunities and develop arbitrage trading strategies, as well as programmatic algorithms automatically release trading orders to the exchange, to quickly and accurately capture opportunities and efficiently earn income, which is the charm of quantitative arbitrage.

This article will teach you how to use FMZ quantitative trading platform and the ETC futures contract in the OkEX exchange in digital currency trading, with a simple arbitrage strategy to demonstrate how to capture the instantaneous arbitrage opportunities, and seize every visible profit while simultaneously hedging the risks that may be encountered.

NO.4
Create a digital currency intertemporal arbitrage strategy
Difficulty: ordinary level
Strategic environment:
Transaction target: Ethereum classic (ETC)
Spread data: ETC weekly - ETC quarterly
Trading period: 5 minutes
Position match: 1:1
Transaction type: same variety intertemporal
Strategy logic:
Buying-long spread position conditions: If the current account has no positions, and the spread is less than the down trail of boll indicator, place a spread order, which is: buying long ETC weekly, selling short ETC quarterly.

Short-selling spread position conditions: If the current account has no positions and the spread is higher than the up rail of boll indicator, place a spread order, which is: selling short ETC weekly, buying long ETC quarterly.

Closing the buying-long spread position condition: If the current account holds a long ETC weekly position and holds a short ETC quarterly position, and the spread is higher than the middle rail of boll indicator, place a close spread order, which is: selling ETC weekly, buy to cover the ETC quarterly.

Closing the Short-selling spread position condition: If the current account holds a short ETC weekly position and holds a long ETC quarterly position, and the spread is lower than the middle rail of boll indicator, place a close spread order, which is: buy to cover the ETC weekly, selling ETC quarterly.

NO.5
The above is a simple description of digital currency intertemporal arbitrage strategy logic, then how to implement your own ideas in the program? We tried to build the framework first on FMZ quantitative trading platform.
Strategy framework:

The strategy framework can be easily built up according to the strategic thinking and transaction process. The entire strategy can be simplified into three steps:

  1. Pre-processing before transaction.
  2. Get and calculate data.
  3. Place an order and follow up.

NO.6
Next, we need to fill in the necessary details in the strategy framework based on the actual transaction process and transaction details.

First, pre-processing before transaction
Step 1: In the global environment, declare the necessary global variables.
Declare a chart object that configures the chart
Var chart = { }
Call the Chart function to initialize the chart
Var ObjChart = Chart ( chart )
Declare an empty array to store the spread sequence
Var bars = [ ]
Declare a timestamp variable that records historical data
Var oldTime = 0
Step 2: Configure external parameters for the strategy.

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, the latest buying/selling price of arbitrage contract A/B, positive/reverse arbitrage spread

Get the position function: mp ( )
Check through the entire array of positions, return the specified contract and the number of positions in the specified direction. Return false if there is none.

K line and indicator function: boll ( )
Synthesize a new K-line sequence based on the data of positive/reverse arbitrage spread. And return the up/middle/down rail data calculated by the boll indicator.

Order function: trade ( )
Insert the order contract name and trade type, then place the order in the latest buying/selling price and return the result after placing the order. Since it is necessary to simultaneously order two different directions of orders, the latest buying/selling price are converted within the function according to the order contract name.

Cancel order function: cancelOrders ( )
Get array of all pending orders and cancel them one by one. And if there is pending order, return false, and if there is none, return true.

Process holding single contract: isEven ( )
In the case of single-leg situation in the arbitrage trading, it is directly handled by simply closing all the positions. Of course, you can also change into chasing price.

Drawing chart function: drawingChart ( )
Call ObjChart.add ( ) method to draw the necessary market data and indicator data in the chart: up, middle, down rail, positive/reverse arbitrage spread.

Step 4: In the entry function main ( ), execute the pre-processing code before transaction, which only run once after the program starts, including:

filter the information that is not very important in the console SetErrorFilter ( )
Set the digital currency type to be traded exchange.IO ( )
Empty the drawn charts before the program starts ObjChart.reset ( )
Empty the status bar information before the program starts LogProfitReset ( )

NO.7
After defining the above pre-processing before transaction, it is necessary to proceed to the next step, enter the polling mode, and repeat the onTick ( ) function.
And set the sleep time when polling, because some of the digital currency exchange's API has a built-in access limit for a certain period of time.

Second, get and calculate data
Step 1: Get the underlying data object, account balance and boll indicator data for trading logic.

Third, place an order and follow-up
Step 1: Perform the buying and selling operation according to the above strategy logic. First, see if the price and indicator conditions are true, and then see if the position conditions are true, and finally execute the trade ( ) order function.

Step 2: After placing order, it is necessary to deal with abnormal situations such as pending orders and holding single contract, and draw charts.

NO.8
Above, we have created a simple digital currency intertemporal arbitrage strategy through more than 200 lines. The complete code is as follows:

// global variable
// Declare a chart object that configures the chart
var chart = {
    __isStock: true,
    tooltip: {
        xDateFormat: '%Y-%m-%d %H:%M:%S, %A'
    },
    title: {
        text: 'Profit and loss chart(detail)'
    },
    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: 'spread'
        },
        opposite: false,
    },
    series: [{
        name: "up",
        id: "line1,up",
        data: []
    }, {
        name: "middle",
        id: "line2,middle",
        data: []
    }, {
        name: "down",
        id: "line3,down",
        data: []
    }, {
        name: "basb",
        id: "line4,basb",
        data: []
    }, {
        name: "sabb",
        id: "line5,sabb",
        data: []
    }]
};
var ObjChart = Chart(chart); // Drawing object
var bars = []; // Store spread sequence
var oldTime = 0; // Record historical data timestamp

// Parameter
var tradeTypeA = "this_week"; // Arbitrage contract A
var tradeTypeB = "quarter"; // Arbitrage contract B
var dataLength = 10; //Length of indicator cycle
var timeCycle = 1; // The cycle of K-line
var name = "ETC"; // Currency type
var unit = 1; // Quantity of orders

// Basic data
function Data(tradeTypeA, tradeTypeB) { // input arbitrage contract A&B
    this.accountData = _C(exchange.GetAccount); // get account data
    this.positionData = _C(exchange.GetPosition); // get position data
    var recordsData = _C(exchange.GetRecords); //get k-line data
    exchange.SetContractType(tradeTypeA); // subscribe arbitrage contract A
    var depthDataA = _C(exchange.GetDepth); // deep data of arbitrage contract A
    exchange.SetContractType(tradeTypeB); // subscribe arbitrage contract B
    var depthDataB = _C(exchange.GetDepth); // deep data of arbitrage contract B
    this.time = recordsData[recordsData.length - 1].Time; // get the latest time data
    this.askA = depthDataA.Asks[0].Price; // the latest selling price of arbitrage contract A
    this.bidA = depthDataA.Bids[0].Price; // the latest buying price of arbitrage contract A
    this.askB = depthDataB.Asks[0].Price; // the latest selling price of arbitrage contract B
    this.bidB = depthDataB.Bids[0].Price; // the latest buying price of arbitrage contract B
    // Positive arbitrage spread(the latest selling price of contract A -the latest buying price of contract B )
    this.basb = depthDataA.Asks[0].Price - depthDataB.Bids[0].Price;
    // Reverse arbitrage spread(the latest buying price of contract A -the latest selling price of contract B )
    this.sabb = depthDataA.Bids[0].Price - depthDataB.Asks[0].Price;
}

// get position information
Data.prototype.mp = function (tradeType, type) {
    var positionData = this.positionData; // get position data
    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;
}

// Synthetize new K-line data and boll indicator data
Data.prototype.boll = function (num, timeCycle) {
    var self = {}; // Temporary object
    // the median of Positive arbitrage spread and reverse arbitrage spread
    self.Close = (this.basb + this.sabb) / 2;
    if (this.timeA == this.timeB) {
        self.Time = this.time;
    } // Comparing two depth data timestamps
    if (this.time - oldTime > timeCycle * 60000) {
        bars.push(self);
        oldTime = this.time;
    } // According to the specified time period, insert the spread data object in the K-line array.
    if (bars.length > num * 2) {
        bars.shift(); // Control K-line array length
    } 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], // up rail of boll indicator
        middle: boll[1][boll[1].length - 1], // middle rail of boll indicator
        down: boll[2][boll[2].length - 1] // down rail of boll indicator
    } // Return a processed boll indicator data.
}

// place order
Data.prototype.trade = function (tradeType, type) {
    exchange.SetContractType(tradeType); // Resubscribe contract before placing order
    var askPrice, bidPrice;
    if (tradeType == tradeTypeA) { // if it's contract A
        askPrice = this.askA; // set askPrice
        bidPrice = this.bidA; // set bidPrice
    } else if (tradeType == tradeTypeB) { // if it's contract B
        askPrice = this.askB; // set askPrice
        bidPrice = this.bidB; // set bidPrice
    }
    switch (type) { // Match order mode
        case "buy":
            exchange.SetDirection(type); // Set order mode
            return exchange.Buy(askPrice, unit);
        case "sell":
            exchange.SetDirection(type); // Set order mode
            return exchange.Sell(bidPrice, unit);
        case "closebuy":
            exchange.SetDirection(type); // Set order mode
            return exchange.Sell(bidPrice, unit);
        case "closesell":
            exchange.SetDirection(type); // Set order mode
            return exchange.Buy(askPrice, unit);
        default:
            return false;
    }
}

// cancel order
Data.prototype.cancelOrders = function () {
    Sleep(500); // delay before canceling, because some exchanges you know...
    var orders = _C(exchange.GetOrders); // Get the array of pending orders
    if (orders.length > 0) { // if there is pending order
        for (var i = 0; i < orders.length; i++) { //check through the array of pending orders
            exchange.CancelOrder(orders[i].Id); //cancel pending orders one by one
            Sleep(500); //Delay 0.5 seconds
        }
        return false; // return false if pending orders have been cancelled
    }
    return true; //return true if there is no pending order
}

// handle holding single contract
Data.prototype.isEven = function () {
    var positionData = this.positionData; // get position data
    var type = null; // converse position direction 
    // If the length of the position array divided by some number and the remainder is 2, the result is not equal to 0 or the length of the position array is not equal to 2
    if (positionData.length % 2 != 0 || positionData.length != 2) {
        for (var i = 0; i < positionData.length; i++) { // check through the array of positions
            if (positionData[i].Type == 0) { // if it's long position
                type = 10; // Set order parameters
            } else if (positionData[i].Type == 1) { // if it's short position
                type = -10; // Set order parameters
            }
            // close all positions
            this.trade(positionData[i].ContractType, type, positionData[i].Amount);
        }
    }
}

// drawing chart
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 condition
function onTick() {
    var data = new Data(tradeTypeA, tradeTypeB); // Create a base 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
    // Spread description
    // basb = (the latest selling price of contract A - the latest buying price of contract B)
    // sabb = (the latest buying price of contract A - the latest selling price of contract B)
    if (data.sabb > boll.middle && data.sabb < boll.up) { // if sabb is higher than the middle rail
        if (data.mp(tradeTypeA, 0)) { // check if contract A has long positon before placing order
            data.trade(tradeTypeA, "closebuy"); // close long position of contract A
        }
        if (data.mp(tradeTypeB, 1)) { // check if contract B has short positon before placing order
            data.trade(tradeTypeB, "closesell"); // close short position of contract B
        }
    } else if (data.basb < boll.middle && data.basb > boll.down) { // if basb is lower than the middle rail
        if (data.mp(tradeTypeA, 1)) { // check if contract A has short positon before placing order
            data.trade(tradeTypeA, "closesell"); // close short position of contract A
        }
        if (data.mp(tradeTypeB, 0)) { // check if contract B has long positon before placing order
            data.trade(tradeTypeB, "closebuy"); // close long position of contract B
        }
    }
    if (accountStocks * Math.max(data.askA, data.askB) > 1) { // If there is balance in the account
        if (data.basb < boll.down) { // if basb spread is lower than the down rail
            if (!data.mp(tradeTypeA, 0)) { // check if contract A has long positon before placing order
                data.trade(tradeTypeA, "buy"); // open long position of contract A
            }
            if (!data.mp(tradeTypeB, 1)) { // check if contract B has short positon before placing order
                data.trade(tradeTypeB, "sell"); // open short position of contract B
            }
        } else if (data.sabb > boll.up) { // if sabb spread is higher than the up rail
            if (!data.mp(tradeTypeA, 1)) { // check if contract A has short positon before placing order
                data.trade(tradeTypeA, "sell"); // open short position of contract A
            }
            if (!data.mp(tradeTypeB, 0)) { // check if contract B has long positon before placing order
                data.trade(tradeTypeB, "buy"); // open long position of contract B
            }
        }
    }
    data.cancelOrders(); // cancel orders
    data.drawingChart(boll); // drawing chart
    data.isEven(); // process holding single contract
}

//enter function
function main() {
    // filter the information that is not very important in the console
    SetErrorFilter("429|GetRecords:|GetOrders:|GetDepth:|GetAccount|:Buy|Sell|timeout|Futures_OP");
    exchange.IO("currency", name + '_USDT'); //Set the cryptocurrency type to be traded
    ObjChart.reset(); //Empty the drawn charts before the program starts
    LogProfitReset(); //Empty the status bar information before the program starts
    while (true) { // Enter polling mode
        onTick(); // Execute onTick function
        Sleep(500); // sleep for o.5 seconds
    }
}

NO.9
This strategy just serves as a trigger. The real market is not that simple, but you can use this example to play with your imagination.

What needs to be reminded is that, based on my limited experience, the pure period arbitrage strategy is basically not worth running in the current digital currency market situation, whether it is risk-free triangle arbitrage or cross-market arbitrage.

The reason is that no matter in which digital currency exchange's futures market, the margin is not fiat. Almost all digital currencies have fallen by about 70% since the beginning of the year. In other words, the strategy is always making currency, but the price of the currency is falling.

Take a broad view, the digital currency market has already left the blockchain. Just like the tulips then, the price always comes from people's expectations and confidence, and the confidence comes from the price...

Please see more at: https://www.fmz.com/bbs-topic/2281

Leave a Reply

Your email address will not be published. Required fields are marked *