Some Thoughts on the Logic of Crypto Currency Futures Trading
Tags: cryptocurrency, trading, futures, fmzquant
Published: 2024-06-14 03:27:13

## Problem scene
For a long time, the data delay problem of the API interface of the crypto currency exchange has always troubled me. I haven't found a suitable way to deal with it. I will reproduce the scene of this problem.

Usually the market order provided by the contract exchange is actually the counterparty price, so sometimes the so-called "market order" is somewhat unreliable. Therefore, when we write crypto currency futures trading strategies, most of them use limit orders. After each order is placed, we need to check the position to see if the order is filled and the corresponding position is held.

The problem lies in this position information. If the order is closed, the data returned by the exchange position information interface (that is, the exchange interface that the bottom layer actually accesses when we call exchange.GetPosition) should contain the information of the newly opened position, but If the data returned by the exchange is old data, that is, the position information of the order just placed before the transaction is completed, this will cause a problem.

The trading logic may consider that the order has not been filled and continue to place the order. However, the order placement interface of the exchange is not delayed, but the transaction is fast, and the order is executed. This will cause a serious consequence that the strategy will repeatedly place orders when triggering the operation of opening a position.

## Actual Experience
Because of this problem, I have seen a strategy to fill a long position crazy, fortunately, the market was risen at that time, and the floating profit once exceeded 10BTC. Fortunately, the market has skyrocketed. If it is a plunge, the ending can be imagined.

## Try To Solve
- Plan 1
It is possible to design the logic of order placement for the strategy to place only one order. The order placing price is a large slippage for the price gap of opponent price at the time, and a certain depth of opponent orders can be executed. The advantage of this is that only one order is placed, and it is not judged based on position information. This can avoid the problem of repeated placing orders, but sometimes when the price changes relatively large, the order will trigger the exchange's price limit mechanism, and it may lead to that the large slippage order is still not completed, and missed the trading opportunity.

- Plan 2
Using the "market price" function of the exchange, the price pass -1 on the FMZ is the "market price". At present, the OKEX futures interface has been upgraded to support "real market price".

- Plan 3
We still use the previous trading logic and place a limit order, but we add some detection to the trading logic to try to solve the problem caused by the delay of the position data. After the order is placed, if the order is not cancelled, it disappears directly in the list of pending orders (the list of pending orders disappears in two possible ways: 1 withdraw order, 2 executed), detect such situation and place the order amount again. The amount of the last order is the same. At this time, it is necessary to pay attention to whether the position data is delayed. Let the program enter the waiting logic to reacquire the position information. You can even continue to optimize and increase the number of triggering waits. If it exceeds a certain number of times, the position interface data is delayed. The problem is serious, let the transaction logic terminate.

## Design based on Plan 3
```
// Parameter
/*
var MinAmount = 1
var SlidePrice = 5
var Interval = 500
*/

function GetPosition(e, contractType, direction) {
    e.SetContractType(contractType)
    var positions = _C(e.GetPosition);
    for (var i = 0; i < positions.length; i++) {
        if (positions[i].ContractType == contractType && positions[i].Type == direction) {
            return positions[i]
        }
    }

    return null
}

function Open(e, contractType, direction, opAmount) {
    var initPosition = GetPosition(e, contractType, direction);
    var isFirst = true;
    var initAmount = initPosition ? initPosition.Amount : 0;
    var nowPosition = initPosition;
    var directBreak = false 
    var preNeedOpen = 0
    var timeoutCount = 0
    while (true) {
        var ticker = _C(e.GetTicker)
        var needOpen = opAmount;
        if (isFirst) {
            isFirst = false;
        } else {
            nowPosition = GetPosition(e, contractType, direction);
            if (nowPosition) {
                needOpen = opAmount - (nowPosition.Amount - initAmount);
            }
            // Detect directBreak and the position has not changed
            if (preNeedOpen == needOpen && directBreak) {
                Log("Suspected position data is delayed, wait 30 seconds", "#FF0000")
                Sleep(30000)
                nowPosition = GetPosition(e, contractType, direction);
                if (nowPosition) {
                    needOpen = opAmount - (nowPosition.Amount - initAmount);
                }
                /*
                timeoutCount++
                if (timeoutCount > 10) {
                    Log("Suspected position delay for 10 consecutive times, placing order fails!", "#FF0000")
                    break
                }
                */
            } else {
                timeoutCount = 0
            }
        }
        if (needOpen < MinAmount) {
            break;
        }
        
        var amount = needOpen;
        preNeedOpen = needOpen
        e.SetDirection(direction == PD_LONG ? "buy" : "sell");
        var orderId;
        if (direction == PD_LONG) {
            orderId = e.Buy(ticker.Sell + SlidePrice, amount, "Open long position", contractType, ticker);
        } else {
            orderId = e.Sell(ticker.Buy - SlidePrice, amount, "Open short position", contractType, ticker);
        }

        directBreak = false
        var n = 0
        while (true) {
            Sleep(Interval);
            var orders = _C(e.GetOrders);
            if (orders.length == 0) {
                if (n == 0) {
                    directBreak = true
                }
                break;
            }
            for (var j = 0; j < orders.length; j++) {
                e.CancelOrder(orders[j].Id);
                if (j < (orders.length - 1)) {
                    Sleep(Interval);
                }
            }
            n++
        }
    }

    var ret = {
        price: 0,
        amount: 0,
        position: nowPosition
    };
    if (!nowPosition) {
        return ret;
    }
    if (!initPosition) {
        ret.price = nowPosition.Price;
        ret.amount = nowPosition.Amount;
    } else {
        ret.amount = nowPosition.Amount - initPosition.Amount;
        ret.price = _N(((nowPosition.Price * nowPosition.Amount) - (initPosition.Price * initPosition.Amount)) / ret.amount);
    }
    return ret;
}

function Cover(e, contractType, opAmount, direction) {
    var initPosition = null;
    var position = null;
    var isFirst = true;

    while (true) {
        while (true) {
            Sleep(Interval);
            var orders = _C(e.GetOrders);
            if (orders.length == 0) {
                break;
            }
            for (var j = 0; j < orders.length; j++) {
                e.CancelOrder(orders[j].Id);
                if (j < (orders.length - 1)) {
                    Sleep(Interval);
                }
            }
        }

        position = GetPosition(e, contractType, direction)
        if (!position) {
            break
        }
        if (isFirst == true) {
            initPosition = position;
            opAmount = Math.min(opAmount, initPosition.Amount)
            isFirst = false;
        }

        var amount = opAmount - (initPosition.Amount - position.Amount)
        if (amount <= 0) {
            break
        }

        var ticker = _C(exchange.GetTicker)
        if (position.Type == PD_LONG) {
            e.SetDirection("closebuy");
            e.Sell(ticker.Buy - SlidePrice, amount, "Close long position", contractType, ticker);
        } else if (position.Type == PD_SHORT) {
            e.SetDirection("closesell");
            e.Buy(ticker.Sell + SlidePrice, amount, "Close short position", contractType, ticker);
        }

        Sleep(Interval)
    }

    return position
}

$.OpenLong = function(e, contractType, amount) {
    if (typeof(e) == "string") {
        amount = contractType
        contractType = e
        e = exchange
    }

    return Open(e, contractType, PD_LONG, amount);
}

$.OpenShort = function(e, contractType, amount) {
    if (typeof(e) == "string") {
        amount = contractType
        contractType = e
        e = exchange
    }

    return Open(e, contractType, PD_SHORT, amount);
};

$.CoverLong = function(e, contractType, amount) {
    if (typeof(e) == "string") {
        amount = contractType
        contractType = e
        e = exchange
    }

    return Cover(e, contractType, amount, PD_LONG);
};

$.CoverShort = function(e, contractType, amount) {
    if (typeof(e) == "string") {
        amount = contractType
        contractType = e
        e = exchange
    }

    return Cover(e, contractType, amount, PD_SHORT);
};


function main() {
    Log(exchange.GetPosition())
    var info = $.OpenLong(exchange, "quarter", 100)
    Log(info, "#FF0000")

    Log(exchange.GetPosition())
    info = $.CoverLong(exchange, "quarter", 30)
    Log(exchange.GetPosition())
    Log(info, "#FF0000")

    info = $.CoverLong(exchange, "quarter", 80)
    Log(exchange.GetPosition())
    Log(info, "#FF0000")
}
```
Template address: https://www.fmz.com/strategy/203258

The way to call the template interface is just like $.OpenLong and $.CoverLong in the main function above.

The template is a beta version, any suggestions are welcome, i will continue to optimize to deal with the problem of delays in position data.

From: https://blog.mathquant.com/2020/06/10/some-thoughts-on-the-logic-of-crypto-currency-futures-trading.html