Проблема задержки данных интерфейса API позиции цифровой валютной биржи долгое время беспокоила меня. Я пока не нашел подходящего решения, поэтому воспроизведу эту проблему. Обычно рыночный ордер, предоставляемый биржей контрактов, на самом деле является ценой контрагента, поэтому иногда использование этого так называемого «рыночного ордера» ненадежно. Поэтому при написании стратегий торговли фьючерсами на цифровые валюты большинство из нас используют лимитные ордера. После размещения каждого ордера нам необходимо проверить позицию, чтобы увидеть, был ли исполнен ордер и удерживается ли соответствующая позиция. Проблема заключается в этой информации о позиции. Если заказ выполнен, данные, возвращаемые интерфейсом информации о позиции обмена (то есть интерфейсом обмена, к которому мы фактически обращаемся, когда вызываем exchange.GetPosition), должны включать информацию о новой открытой позиции. Однако, если данные, возвращаемые биржей, являются старыми данными, то есть информацией о позиции до того, как был выполнен только что размещенный ордер, то возникнет проблема. Торговая логика может посчитать, что ордер не был исполнен, и продолжить его размещение. Однако интерфейс размещения заказов биржи не испытывает никаких задержек. Вместо этого транзакции завершаются очень быстро, а заказы исполняются сразу после их размещения. Это приведет к серьезным последствиям, поскольку стратегия будет неоднократно размещать ордера при запуске операции открытия.
Из-за этой проблемы я видел стратегию, которая открыла сумасшедшую полную длинную позицию. К счастью, рынок в то время был на подъеме, и плавающая прибыль однажды превысила 10BTC. К счастью, рынок процветает, если бы он резко упал, результат был бы предсказуем.
Решение 1 Логика ордера стратегии может быть разработана для размещения только одного ордера, а цена ордера будет равна цене оппонента на тот момент плюс большее проскальзывание, чтобы принимать ордера оппонента определенной глубины. Преимущество этого способа в том, что вы размещаете только один ордер, и он не основан на информации о позиции. Это может помочь избежать проблемы дублирования ордеров, но иногда размещение ордера в момент значительного изменения цены может привести к срабатыванию механизма ограничения цены биржи, и вполне возможно, что даже при большом проскальзывании ордер все равно не будет исполнен, тем самым упуская возможность. .
Решение 2 Используйте функцию рыночного ордера биржи и передайте -1 на FMZ в качестве цены, что является рыночным ордером. В настоящее время интерфейс фьючерсов OKEX был обновлен для поддержки реальных рыночных ордеров.
Решение 3 Мы по-прежнему используем предыдущую торговую логику и размещаем ордера с использованием лимитных ордеров, но добавляем некоторые обнаружения в торговую логику, чтобы попытаться решить проблему, вызванную задержкой данных о позиции. Проверьте, исчезает ли заказ из списка отложенных заказов без отмены после размещения заказа (есть две возможности исчезновения из списка отложенных заказов: 1 отмена и 2 выполнение). Если такая ситуация обнаружена и количество заказа и Заказ Объем такой же, как и в прошлый раз. В это время следует обратить внимание на то, задерживаются ли данные о позиции. Позвольте программе войти в логику ожидания и повторно получить информацию о позиции. Вы даже можете продолжить оптимизацию и увеличить количество ожиданий срабатывания. Если оно превышает определенное число, это означает, что данные интерфейса позиции задерживаются. Проблема серьезная, поэтому логика этой транзакции прекращается.
// 参数
/*
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
。
Шаблон находится в стадии бета-версии. Предложения и комментарии приветствуются. Мы продолжим его оптимизировать, чтобы решить проблему задержки данных о местоположении.