Problem scenario
For a long time, the data delay problem of the position API interface of the digital currency exchange has always bothered me. I haven't found a proper way to deal with this problem. Let me reproduce the scene of the problem. Usually, the market price order provided by the contract exchange is actually the counterparty price, so sometimes it is not reliable to use this so-called "market price order". Therefore, when we write digital currency futures trading strategies, we use the limit order most. After each order is placed, we need to check the position to see if the order is closed and the corresponding position is held. The problem lies in this position information. If the order is closed, the data returned by the position information interface of the exchange (that is, the exchange interface actually accessed by the bottom layer when we call exchange.GetPosition) should include the new position information. However, if the data returned by the exchange is the old data, that is, the position information before the order just placed is closed, there exists a problem. The trading logic may deem that the order has not been completed, and continue to place the order. However, the interface for the exchange to place an order is not delayed. Instead, the transaction is filled very fast, and the order is placed. This will cause a serious consequence that the strategy will place orders repeatedly when triggering the opening position operation once.
Practical experience
Because of this problem, I have seen a strategy open full of long positions crazily. Fortunately, the market soared at that time, and the floating profit exceeded 10BTC. Fortunately, the market increased sharply. If it decreased sharply, we can imagine the outcome.
Solutions
Solution 1
The strategy can be designed to place only one order, and the order price is the price of the current trading opponent plus a large sliding price, so as to take a certain depth of the opponent's order. The advantage of this is that only one order will be placed, and it will not be judged based on the position information. This can avoid the problem of repeated orders, but sometimes placing an order may trigger the price limit mechanism of the exchange when the price change is relatively large, and it is possible to increase the sliding price and still fail to make a deal, thus missing the opportunity.Solution 2
With the market price order function of the exchange, the price is transferred to - 1 on the FMZ as the market price order. At present, the OKEX futures interface has been upgraded to support the real market price order.Solution 3
We still use the previous trading logic and place orders with price limit orders, but we add some detection in the trading logic to try to solve the problem caused by the position data delay. Check whether the order has disappeared directly from the list of pending orders without cancellation (there are two possibilities for disappearance from the list of pending orders: 1. Cancellation and 2. Filled). If such a situation is detected, and the quantity of the order placed again is the same as that of the last order, it is important to note that whether the position data is delayed. Let the program enter the waiting logic to reacquire the position information, or even continue to optimize and increase the number of times to trigger the waiting, if it exceeds a certain number of times, it indicates that the position interface data delay is serious, which causes the trading logic to terminate.
Design based on Solution 3
pine// Parameters
/*
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);
}
// Check directBreak and the position remains unchanged
if (preNeedOpen == needOpen && directBreak) {
Log("Suspected position data is delayed, wait for 30 seconds", "#FF0000")
Sleep(30000)
nowPosition = GetPosition(e, contractType, direction);
if (nowPosition) {
needOpen = opAmount - (nowPosition.Amount - initAmount);
}
/*
timeoutCount++
if (timeoutCount > 10) {
Log("Suspected position is delayed for 10 consecutive times, and the order is failed!", "#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 positions", contractType, ticker);
} else {
orderId = e.Sell(ticker.Buy - SlidePrice, amount, "open short positions", 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 positions", contractType, ticker);
} else if (position.Type == PD_SHORT) {
e.SetDirection("closesell");
e.Buy(ticker.Sell + SlidePrice, amount, "close short positions", 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 template interface is called in the same way as $.OpenLong
, $.CoverLong
in the main function above.
The template is a beta version, and you are welcome to make suggestions. We will continue to optimize it so that we can handle the problem of position data delay..