长久以来,数字货币交易所持仓API接口的数据延迟问题总是困扰着我。一直没有找到合适的处理方式,这个问题的场景我来复现下。通常合约交易所提供的市价单其实为对手价,所以有时候用这个所谓的“市价单”有些不靠谱。因此我们在写数字货币期货交易策略时,大部分用的是限价单。在每次下单后,我们要检查持仓,看看下单是不是成交了,并且持有了对应的仓位。问题就出在这个持仓信息上,如果订单成交了,交易所持仓信息接口(就是我们调用exchange.GetPosition时底层实际去访问的交易所接口)返回的数据应当是包含新开仓持仓信息的,但是交易所返回的数据如果是旧数据,即刚才下单的订单成交前的持仓信息,这样就出问题了。交易逻辑可能认为订单没有成交,继续下单。但是交易所下单接口并不延迟,反而成交很快,下单就成交。这样会造成一种严重的后果就是策略在一次触发开仓的操作时会不停的重复下单。
이 문제로 인해 전략적으로 많은 포지션을 열었고, 다행히 시장이 폭락했을 때 10BTC를 넘어서게 되었습니다.
방안 1 전략은 다음 주문에만 주문할 수 있도록 설계할 수 있으며, 주문 가격은 그 당시 거래 상대의 가격에 더 큰 슬라이드 가격을 추가하여 특정 깊이의 주문을 먹을 수 있습니다. 그렇게하는 장점은 다음 주문에만 있으며, 보유 정보에 따라 판단되지 않습니다. 이것은 반복 주문 문제를 피할 수 있습니다. 그러나 때로는 가격 변동이 더 큰 경우 주문은 거래소의 가격 제한 장치를 유발할 수 있으며, 큰 슬라이드 가격을 추가 할 수도 있습니다.
방안 2 거래소의 시가표 기능으로 FMZ에서 가격 전송-1은 시가표이며, 현재 OKEX 선물 인터페이스는 진정한 시가표를 지원하기 위해 업그레이드되었습니다.
방안 3 우리는 여전히 이전 거래 논리를 사용하고, 한정 가격 주문을 주문하지만, 우리는 거래 논리에 몇 가지 검사를 추가하여 이 포지션 데이터 지연의 문제를 해결하려고 시도했습니다. 포지션 데이터 지연의 문제를 해결하려고 노력했습니다. 포지션 데이터 지연의 문제를 해결하려고 노력했습니다. 포지션 데이터 지연의 문제를 해결하려고 노력했습니다. 포지션 데이터 지연의 문제를 해결하려고 노력했습니다.
// 参数
/*
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")
}
템플릿 주소:https://www.fmz.com/strategy/203258
템플릿 인터페이스를 호출하는 방법은$.OpenLong
,$.CoverLong
ᅳ
템플릿은 테스트 버전이며 제안은 환영합니다.
excm가장 좋은 방법은 WS를 사용해서 업데이트가 되면 서버가 한번에 물어보는 대신 즉시 알려주는 것입니다.
Xueqiu Bot이 문제를 겪어본 적이 있는데, 내 해결책은 주문 전 보유를 기록하고, ioc 주문 후 id를 기록하고, 순환에서 주문 상태를 얻으며, 거래/부정 거래인 경우, 순환은 현재 보유와 기록된 보유를 대조하고, 두 값이 다르면 순환에서 벗어납니다.
발명가들의 수량화 - 작은 꿈이 모든 것은 우익과 불익을 가지고 있습니다.
excmws는 기본적으로 문제가되지 않습니다. 중요한 시점에 연결이 끊어지면도, 좋은 연결 메커니즘을 구축하는 데 문제가 없습니다. 물론 완전한 해결책도 있습니다.
발명가들의 수량화 - 작은 꿈그러나, WS 인터페이스가 신뢰할 수 없는 경우에도 REST 프로토콜보다 더 많은 문제를 야기합니다.
발명가들의 수량화 - 작은 꿈좋은 감사합니다, 계속 공부하세요.