BitMEX has become the platform of choice for cryptocurrency leverage trading, but its API trading restrictions are strict and make automatic traders feeling very confused. This article mainly shares some tips on the use of APIs in the FMZ quantitative trading platform, mainly for the market making strategy.

1. Features of BitMEX

The most significant advantage is that the trading liquidity is very active, especially the Bitcoin perpetual contract, the transaction amount per minute often exceeds one million or even ten million US dollars; BitMEX pending orders trading have the policy of return commission fee, although it is not much, but attracted a large number of market making tradings, which made the price depth very rich. the latest buying and selling price often have more than one million dollars worth pending orders; because of this point, the transaction price often fluctuates around the minimum change unit of $0.50.

2.BitMEX API frequency limit

The request frequency of the REST API is limited to 300 times every 5 minutes, almost equal to 1 time every second, this limit can be said to be very strict compared to other trading platforms. After the limit is exceeded, 'Rate limit exceeded' will be prompted. If you keep exceeding the limit, the IP may be disabled for one hour. Multiple disables in a short time will result in a week being disabled. For each API request, BitMEX will return the header data, header data is used to see the current number of remaining requests. In fact, if the API is used properly, it will not exceed the frequency limit and generally does not need to be checked.

3.Use websocket to get the market quote

The BitMEX REST API is more restrictive. The official recommendation is to use the websocket protocol more, and push more data types than the average exchange. Pay attention to the following points for specific use:

If the depth data push time is too long, there will be an error, which does not correspond to the real depth. It is estimated that there are too many depth changes and there are omissions in the push, but in general, due to excellent fluidity, you can subscribe to "ticker" or "trades".
The order details push is missing a lot and is almost unavailable.
There is a significant delay in the push of account information, preferably using the REST API.
When the market is volatile too big, the push delay will reach a few seconds.
The following code uses the websocket protocol to obtain market and account information in real time, mainly for market-making strategies. The specific use needs to be performed in the main() function.

var ticker  = {price:0, buy:0, sell:0, time:0} //Ticker information, the latest price, "buy one" price, "sell one" price, update time
//Account information, respectively, position, buying and selling price, buying and selling quantity, position status, order Id
var info = {position:0, buyPrice:0, sellPrice:0, buyAmount:0, sellAmount:0, buyState:0, sellState:0, buyId:0, sellId:0}
var buyListId = []//Global variables, pre-emptive buying id list, will described below
var sellListId = []
var APIKEY = 'your api id' //Need to fill in the BitMEX API ID here. Note that it is not a key, which is required for websocket protocol authentication.
var expires = parseInt(Date.now() / 1000) + 10
var signature = exchange.HMAC("sha256", "hex", "GET/realtime" + expires, "{{secretkey}}")//The secretkey will be automatically replaced at the bottom level and does not need to be filled in.
var bitmexClient = Dial("wss://www.bitmex.com/realtime", 60)
var auth = JSON.stringify({args: [APIKEY, expires, signature], op: "authKeyExpires"})//Authentication information, otherwise you cannot subscribe to the account
bitmexClient.write(auth)
bitmexClient.write('{"op": "subscribe", "args": ["position","execution","trade:XBTUSD"]}')//Subscribed to positions, order execution and perpetual contract real-time transaction
while(true){
    if(bitmexClient.read()){
        bitmexData = JSON.parse(data)
        if('table' in bitmexData && bitmexData.table == 'trade'){
            data = bitmexData.data
            ticker.price = parseFloat(data[data.length-1].price)//The latest transaction price, will push multiple transactions at a time, take one will be ok
            //You can get the "buy one" and "sell one" price according to the direction of the latest transaction, without subscribing to the depth.
            if(data[data.length-1].side == 'Buy'){
                ticker.sell = parseFloat(data[data.length-1].price)
                ticker.buy = parseFloat(data[data.length-1].price)-0.5
            }else{
                ticker.buy = parseFloat(data[data.length-1].price)
                ticker.sell = parseFloat(data[data.length-1].price)+0.5
            }
            ticker.time =  new Date(data[data.length-1].timestamp);//Update time, can be used to determine the delay
        }
    }else if(bitmexData.table == 'position'){
        var position = parseInt(bitmexData.data[0].currentQty)  
        if(position != info.position){
            Log('Position change: ', position, info.position, '#FF0000@')//Position change Log, and pushed to WeChat, remove @ means Do not push
            info.position = position  
        }
        info.position  = parseInt(bitmexData.data[0].currentQty)  
    }
}

4. Placing order skills

BitMEX officially recommends using "bulk ordering" and "order modification" to place order. "bulk ordering" can be executed faster due to BitMEX real-time auditing, risk checking, margin calculation, and commissioning. Therefore, the frequency of the "bulk ordering" is calculated as one tenth of the normal frequency. Futhermore, our order operation should use the method of "bulk ordering" and "order modification" to minimize the use of API. The query order status also needs to consume the API using frequency. It can judge the order status according to the position change or modification order failure.

"bulk ordering" does not limit the order quantity (can't be too much), in fact, a single order can also use the "bulk ordering" interface. Due to the operation of modifying the order, we can "pre-order" some orders where the price deviates greatly, these orders will not be executed, but when we need to place an order, we only need to modify the price and quantity of the placed order. when modifying the order occurs failure, it can also be used as a signal for the order to be executed.

The following is the specific implementation code:

// Cancel all orders and reset global variables
function cancelAll(){
    exchange.IO("api","DELETE","/api/v1/order/all","symbol=XBTUSD")//Call IO extension revocation
    info = {position:0, buyPrice:0, sellPrice:0, buyAmount:0, sellAmount:0, buyState:0, sellState:0, buyId:0, sellId:0}
    buyListId = []
    sellListId = []
}
//placing alternate order
function waitOrders(){
    var orders = []
    if(buyListId.length<4){
        //When the number of inspections is insufficient, place another "bulk"
        for(var i=0;i<7;i++){
            //Due to BitMEX restrictions, the price can not be excessively excessive, the order quantity can not be too small, and the "execInst" parameter guarantees that only the market making transaction can be executed.
            orders.push({symbol:'XBTUSD', side:'Buy', orderQty:100, price:ticker.buy-400+i, execInst:'ParticipateDoNotInitiate'})
        }
    }
    if(sellListId.length<4){
        for(var i=0;i<7;i++){
            orders.push({symbol:'XBTUSD', side:'Sell', orderQty:100, price:ticker.buy+400+i, execInst:'ParticipateDoNotInitiate'})
        }
    }
    if(orders.length>0){
        var param = "orders=" + JSON.stringify(orders);
        var ids = exchange.IO("api", "POST", "/api/v1/order/bulk", param);//Bulk orders submitted here
        for(var i=0;i<ids.length;i++){
            if(ids.side == 'Buy'){
                buyListId.push(ids.orderID)
            }else{
                sellListId.push(ids.orderID)
            }
        }
    }
}
//Modify order function
function amendOrders(order, direction, price, amount, id){
    var param = "orders=" + JSON.stringify(order);
    var ret = exchange.IO("api", "PUT", "/api/v1/order/bulk", param);//Modify one order at a time
    //Modification occurs error
    if(!ret){
        var err = GetLastError()
        //overloaded unmodified strategy, need to recycle the order id
        if(err.includes('The system is currently overloaded')){
            if(id){
                if(direction == 'buy'){
                    buyListId.push(id)
                }else{
                    sellListId.push(id)
                }
            }
            Sleep(1000)
            return
        }
        //Illegal order status, indicating that the order to be modified has been completely executed
        else if(err.includes('Invalid ordStatus')){
            Log(order, direction)
            if(direction == 'buy'){
                info.buyId = 0
                info.buyState = 0
                info.buyAmount = 0
                info.buyPrice = 0
            }else{
                info.sellId = 0
                info.sellState = 0
                info.sellAmount = 0
                info.sellPrice = 0
            }
            //Since the push is not timely, update the position with the "rest" protocol here.
            pos = _C(exchange.GetPosition)
            if(pos.length>0){
                info.position = pos[0].Type == 0 ? pos[0].Amount : -pos[0].Amount
            }else{
                info.position = 0
            }
        }
        //Unknown error cannot be modified, all orders are cancelled, reset once
        else if(err.includes('Invalid orderID')){
            cancelAll()
            Log('Invalid orderID,reset once')
        }
        //Exceed the frequency limit, you can continue to try after hibernation
        else if(err.includes('Rate limit exceeded')){
            Sleep(2000)
            return
        }
        //The account is banned, all orders are revoked, and sleep is awaiting recovery for a long time.
        else if(err.includes('403 Forbidden')){
            cancelAll()
            Log('403,reset once')
            Sleep(5*60*1000)
        }
    }else{
        //Modify order successfully
        if(direction == 'buy'){
            info.buyState = 1
            info.buyPrice = price
            info.buyAmount = amount
        }else{
            info.sellState = 1
            info.sellPrice = price
            info.sellAmount = amount
        }
    }
}
//0.5 price change
function fixSize(num){
    if(num>=_N(num,0)+0.75){
        num = _N(num,0)+1
    }else if(num>=_N(num,0)+0.5){
        num=_N(num,0)+0.5
    }else{
        num=_N(num,0)
    }
    return num
}
//Trading function
function trade(){
    waitOrders()//Check if you need a replacement order
    var buyPrice = fixSize(ticker.buy-5) //For demonstration purposes only, specific transactions should be written by yourself.
    var sellPrice = fixSize(ticker.sell+5)
    var buyAmount =  500
    var sellAmount = 500
    //Modify from an alternate order when there is no order
    if(info.buyState == 0  && buyListId.length > 0){
        info.buyId = buyListId.shift()
        amendOrders([{orderID:info.buyId, price:buyPrice, orderQty:buyAmount}],'buy', group, buyPrice, buyAmount, info.buyId)
    }
    if(info.sellState == 0 && sellListId.length > 0){
        info.sellId = sellListId.shift()
        amendOrders([{orderID: info.sellId, price:sellPrice, orderQty:sellAmount}],'sell', group, sellPrice, sellAmount, info.sellId )
    }
    //Existing orders need to change price
    if(buyPrice !=  info.buyPrice && info.buyState == 1){
        amendOrders([{orderID:info.buyId, price:buyPrice, orderQty:buyAmount}],'buy', group, buyPrice, buyAmount)
    }
    if(sellPrice != info.sellPrice && info.sellState == 1){
        amendOrders([{orderID:info.sellId, price:sellPrice, orderQty:sellAmount}],'sell', group, sellPrice, sellAmount)
    }
}

5. Others

BitMEX's server is in the Amazon's server in Dublin, Ireland. The server running strategy ping is less than 1ms when you choose a AWS cloud sever in Dublin, but when there is still a delay in pushing, the overload problem cannot be solved. In addition, when the account is logged in, the server agent cannot be located in the United States and other places where don't allow cryptocurrency tradings. Due to the regulation, the account will be banned.

The code in this article has been modified from my personal strategy and is not guaranteed to be completely correct for reference. The specific use of the market code should be executed in the main function, the trading-related code is placed before the main function, and the trade() function is placed in the push market quote.

Leave a Reply

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