长久以来,数字货币交易所持仓API接口的数据延迟问题总是困扰着我。一直没有找到合适的处理方式,这个问题的场景我来复现下。通常合约交易所提供的市价单其实为对手价,所以有时候用这个所谓的“市价单”有些不靠谱。因此我们在写数字货币期货交易策略时,大部分用的是限价单。在每次下单后,我们要检查持仓,看看下单是不是成交了,并且持有了对应的仓位。问题就出在这个持仓信息上,如果订单成交了,交易所持仓信息接口(就是我们调用exchange.GetPosition时底层实际去访问的交易所接口)返回的数据应当是包含新开仓持仓信息的,但是交易所返回的数据如果是旧数据,即刚才下单的订单成交前的持仓信息,这样就出问题了。交易逻辑可能认为订单没有成交,继续下单。但是交易所下单接口并不延迟,反而成交很快,下单就成交。这样会造成一种严重的后果就是策略在一次触发开仓的操作时会不停的重复下单。
Aufgrund dieses Problems wurde eine Strategie gesehen, die verrückt war, um mehrere Positionen zu öffnen, glücklicherweise wegen des Marktsturzes, der einmal über 10 BTC flog.
Schritt 1 Es ist möglich, die Strategie zu entwerfen, um die Logik des Auftrags nur für den nächsten Auftrag zu entwerfen. Der Auftragspreis wird mit einem größeren Sprungpreis für den Preis des damaligen Auftragsgegenstanders verringert, um den Auftragswert einer bestimmten Tiefe zu nehmen.
Schritt 2 Mit der Marktpreisliste-Funktion der Börse, die auf FMZ als Marktpreisliste bezeichnet wird, wurde die OKEX-Futureschnittstelle aktualisiert, um echte Marktpreislisten zu unterstützen.
Schritt 3 Wir verwenden immer noch die vorherige Transaktionslogik, indem wir einen Limit-Bestellenauftrag verwenden, aber wir fügen einige Tests in die Transaktionslogik hinzu, um zu versuchen, das Problem zu lösen, das durch die Verzögerung der Positionsdaten verursacht wird. Es wird untersucht, ob die Post-Order direkt in der Aufbewahrungsliste verschwunden sind, ohne dass sie widerrufen wurden.
// 参数
/*
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);
}
// 检测directBreak 并且持仓未变的情况
if (preNeedOpen == needOpen && directBreak) {
Log("疑似仓位数据延迟,等待30秒", "#FF0000")
Sleep(30000)
nowPosition = GetPosition(e, contractType, direction);
if (nowPosition) {
needOpen = opAmount - (nowPosition.Amount - initAmount);
}
/*
timeoutCount++
if (timeoutCount > 10) {
Log("连续10次疑似仓位延迟,下单失败!", "#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, "开多仓", contractType, ticker);
} else {
orderId = e.Sell(ticker.Buy - SlidePrice, amount, "开空仓", 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, "平多仓", contractType, ticker);
} else if (position.Type == PD_SHORT) {
e.SetDirection("closesell");
e.Buy(ticker.Sell + SlidePrice, amount, "平空仓", 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")
}
Die Adresse des Templates:https://www.fmz.com/strategy/203258
Die Art und Weise, wie man die Schablonenoberfläche aufruft, ist wie in der oben genannten Main-Funktion.$.OpenLong
,$.CoverLong
Das ist nicht wahr.
Die Vorlage ist in der Testversion und wir freuen uns über Vorschläge, die wir weiter optimieren werden, um Verzögerungen bei Lagerdaten zu bewältigen.
exmDie beste Methode wäre, wenn man ws benutzt, um den Server sofort zu informieren, sobald er ein Update hat, anstatt immer wieder nachzufragen.
Xueqiu BotIch habe dieses Problem schon einmal, und meine Lösung ist es, die Haltungen vor der Bestellung aufzuzeichnen, dann die id nach der ioc-Bestellung aufzuzeichnen, um den Status der Bestellung zu erhalten, wenn es sich um eine Transaktion/Teiltransaktion handelt, um die gegenwärtige Haltung und die Aufzeichnung der Haltungen zu erfassen, und um aus dem Loop zu springen, wenn die beiden Werte unterschiedlich sind.
Die Erfinder quantifizieren - Kleine TräumeIch habe eine instabile WS und verschiedene Störungen erlebt.
exmWenn wir uns in einem kritischen Moment abbrechen, ist es kein Problem, einen guten Wiederverbindungsmechanismus zu bauen; natürlich gibt es auch eine vollständige Lösung, die Sie selbst erforschen müssen.
Die Erfinder quantifizieren - Kleine TräumeDas ist ein Problem, das sich auch bei unzuverlässigen Zeiten mit dem WS-Interface ergibt, was noch mehr Ärger bereitet als beim REST-Protokoll.
Die Erfinder quantifizieren - Kleine TräumeIch möchte Ihnen danken, dass Sie das gelernt haben.