
Por muito tempo, o problema de atraso de dados da interface da API de posição de câmbio de moeda digital sempre me incomodou. Ainda não encontrei uma solução adequada, então reproduzirei este problema. Normalmente, a ordem de mercado fornecida pela bolsa de contratos é, na verdade, o preço da contraparte, portanto, às vezes, usar essa chamada “ordem de mercado” não é confiável. Portanto, quando escrevemos estratégias de negociação de futuros de moeda digital, a maioria de nós usa ordens limitadas. Depois de fazer cada ordem, precisamos verificar a posição para ver se a ordem foi executada e se a posição correspondente foi mantida. O problema está nessas informações de posição. Se a ordem for executada, os dados retornados pela interface de informações de posição de câmbio (ou seja, a interface de câmbio realmente acessada quando chamamos exchange.GetPosition) devem incluir as informações de posição recém-abertas. No entanto, se os dados retornados pela exchange são dados antigos, ou seja, as informações de posição antes da ordem recém-emitida ser executada, então haverá um problema. A lógica de negociação pode pensar que a ordem não foi executada e continuar a colocá-la. No entanto, a interface de colocação de ordens da exchange não sofre atrasos. Em vez disso, as transações são concluídas muito rapidamente, e as ordens são executadas assim que são colocadas. Isso resultará em uma consequência séria: a estratégia emitirá ordens repetidamente quando uma operação de abertura for acionada.
Por causa desse problema, vi uma estratégia que abriu uma posição longa cheia louca. Felizmente, o mercado estava crescendo naquela época e o lucro flutuante uma vez excedeu 10 BTC. Felizmente, o mercado está em alta. Se tivesse despencado, o resultado seria previsível.
Solução 1 A lógica de ordem da estratégia pode ser projetada para colocar apenas uma ordem, e o preço da ordem é o preço do oponente no momento mais um deslizamento maior, de modo a aceitar ordens do oponente de uma certa profundidade. A vantagem de fazer isso é que você só faz uma ordem e ela não é baseada em informações de posição. Isso pode evitar o problema de ordens duplicadas, mas às vezes, colocar uma ordem quando o preço muda muito pode acionar o mecanismo de limite de preço da bolsa, e é possível que mesmo com um grande deslizamento, a ordem ainda não seja executada, perdendo assim a oportunidade .
Solução 2 Use a função de ordem de mercado da bolsa e passe -1 em FMZ como o preço, que é uma ordem de mercado. Atualmente, a interface de futuros da OKEX foi atualizada para suportar ordens de mercado reais.
Solução 3 Ainda usamos a lógica de negociação anterior e colocamos ordens usando ordens limitadas, mas adicionamos algumas detecções à lógica de negociação para tentar resolver o problema causado pelo atraso nos dados de posição. Verifique se o pedido desaparece da lista de pedidos pendentes sem ser cancelado após a realização do pedido (há duas possibilidades de desaparecimento da lista de pedidos pendentes: 1 cancelamento e 2 cumprimento). Se tal situação for detectada e a quantidade do pedido e o pedido o volume é o mesmo da última vez. Neste momento, você deve prestar atenção se os dados de posição estão atrasados. Deixe o programa entrar na lógica de espera para readquirir as informações de posição. Você pode até continuar a otimizar e aumentar o número de esperas de disparo. Se exceder um certo número, significa que os dados da interface de posição estão atrasados. O problema é sério e a lógica desta transação é encerrada.
// 参数
/*
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")
}
Endereço do modelo: https://www.fmz.com/strategy/203258
A maneira de chamar a interface do modelo é a mesma da função principal acima$.OpenLong,$.CoverLong。
O modelo é uma versão beta. Sugestões e comentários são bem-vindos. Continuaremos a otimizá-lo para resolver o problema de atraso de dados de posição.