A lógica da negociação de futuros de criptomoedas

Autora:Bem-estar, Criado: 2020-07-18 13:31:34, Atualizado: 2023-10-26 20:05:28

img

Cenas problemáticas

Durante muito tempo, o problema de atraso de dados da interface API da troca de criptomoedas sempre me preocupou. Eu não encontrei uma maneira adequada de lidar com isso. Vou reproduzir a cena deste problema.

Normalmente, a ordem de mercado fornecida pela troca de contratos é na verdade o preço da contraparte, por isso às vezes a chamada ordem de mercado é um pouco não confiável.

O problema reside nesta informação de posição. se a ordem é fechada, os dados devolvidos pela interface de informação de posição de troca (ou seja, a interface de troca que a camada inferior realmente acessa quando chamamosexchange.GetPosition) deve conter as informações da posição recentemente aberta, mas se os dados devolvidos pela bolsa forem dados antigos, isto é, as informações de posição da ordem acabada de ser colocada antes de a transacção ser concluída, isso causará um problema.

A lógica de negociação pode considerar que a ordem não foi preenchida e continuar a colocar a ordem. No entanto, a interface de colocação de ordens da bolsa não é atrasada, mas a transação é rápida e a ordem é executada. Isso causará uma consequência séria de que a estratégia colocará ordens repetidamente ao desencadear a operação de abertura de uma posição.

Experiência real

Por causa deste problema, eu vi uma estratégia para preencher uma posição longa louca, felizmente, o mercado estava subindo naquele momento, e o lucro flutuante excedeu uma vez 10BTC. Felizmente, o mercado disparou. Se for uma queda, o fim pode ser imaginado.

Tente resolver

  • Plano 1

É possível projetar a lógica de colocação de ordens para que a estratégia coloque apenas uma ordem. O preço de colocação de ordem é um grande deslizamento para a diferença de preço do preço do oponente no momento, e uma certa profundidade de ordens do oponente pode ser executada. A vantagem disso é que apenas uma ordem é colocada, e não é julgada com base em informações de posição. Isso pode evitar o problema de ordens repetidas, mas às vezes, quando o preço muda relativamente grande, a ordem irá ativar o mecanismo de limite de preço da bolsa, e isso pode levar a que a grande ordem de deslizamento ainda não seja concluída e perca a oportunidade de negociação.

  • Plano 2

Utilizando a função preço de mercado da bolsa, a passagem de preço -1 na FMZ é o preço de mercado.

  • Plano 3

Ainda usamos a lógica de negociação anterior e colocamos uma ordem de limite, mas adicionamos alguma detecção à lógica de negociação para tentar resolver o problema causado pelo atraso dos dados da posição. Depois de a ordem ser colocada, se a ordem não for cancelada, ela desaparece diretamente na lista de ordens pendentes (a lista de ordens pendentes desaparece de duas maneiras possíveis: 1 retirada de ordem, 2 executadas), detectamos tal situação e colocamos o valor da ordem novamente. O valor da última ordem é o mesmo. Neste momento, é necessário prestar atenção se os dados da posição estão atrasados. Deixe o programa entrar na lógica de espera para recuperar as informações da posição. Você pode até continuar a otimizar e aumentar o número de espera de gatilho. Se exceder um certo número de vezes, os dados da interface de posição são atrasados. O problema é sério, deixe a lógica de transação terminar.

Projeto baseado no Plano 3

// Parameter
/*
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);
            }
            // Detect directBreak and the position has not changed
            if (preNeedOpen == needOpen && directBreak) {
                Log("Suspected position data is delayed, wait 30 seconds", "#FF0000")
                Sleep(30000)
                nowPosition = GetPosition(e, contractType, direction);
                if (nowPosition) {
                    needOpen = opAmount - (nowPosition.Amount - initAmount);
                }
                /*
                timeoutCount++
                if (timeoutCount > 10) {
                    Log("Suspected position delay for 10 consecutive times, placing order fails!", "#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 position", contractType, ticker);
        } else {
            orderId = e.Sell(ticker.Buy - SlidePrice, amount, "Open short position", 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 position", contractType, ticker);
        } else if (position.Type == PD_SHORT) {
            e.SetDirection("closesell");
            e.Buy(ticker.Sell + SlidePrice, amount, "Close short position", 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 é como$.OpenLonge$.CoverLongemmainfunção acima.

O modelo é uma versão beta, qualquer sugestão é bem-vinda, vou continuar a otimizar para lidar com o problema de atrasos nos dados de posição.


Relacionados

Mais.