La lógica del comercio de futuros de criptomonedas

El autor:La bondad, Creado: 2020-07-18 13:31:34, Actualizado: 2023-10-26 20:05:28

img

Escenario de problemas

Durante mucho tiempo, el problema del retraso de datos de la interfaz API del intercambio de criptomonedas siempre me ha preocupado. No he encontrado una manera adecuada de lidiar con él. Reproduciré la escena de este problema.

Por lo general, la orden de mercado proporcionada por el intercambio de contratos es en realidad el precio de la contraparte, por lo que a veces la llamada orden de mercado es algo poco confiable. Por lo tanto, cuando escribimos estrategias de comercio de futuros de criptomonedas, la mayoría de ellas utilizan órdenes de límite. Después de cada orden se coloca, necesitamos verificar la posición para ver si la orden está llena y se mantiene la posición correspondiente.

Si la orden se cierra, los datos devueltos por la interfaz de información de posición de intercambio (es decir, la interfaz de intercambio que la capa inferior realmente accede cuando llamamosexchange.GetPositionSi los datos devueltos por la bolsa son datos antiguos, es decir, la información de la posición de la orden que acaba de colocarse antes de que se complete la transacción, esto causará un problema.

La lógica de negociación puede considerar que la orden no se ha cumplido y continuar colocando la orden. Sin embargo, la interfaz de colocación de órdenes del intercambio no se retrasa, pero la transacción es rápida y la orden se ejecuta. Esto causará una consecuencia grave de que la estrategia coloque órdenes repetidamente al desencadenar la operación de apertura de una posición.

Experiencia real

Debido a este problema, he visto una estrategia para llenar una posición larga loco, afortunadamente, el mercado había aumentado en ese momento, y la ganancia flotante excedió una vez 10BTC.

Trate de resolverlo

  • El plan 1

Es posible diseñar la lógica de la colocación de órdenes para que la estrategia coloque solo un pedido. El precio de colocación de órdenes es un gran deslizamiento para la brecha de precio del precio del oponente en el momento, y se puede ejecutar una cierta profundidad de órdenes del oponente. La ventaja de esto es que solo se coloca un pedido, y no se juzga en función de la información de posición. Esto puede evitar el problema de la colocación repetida de pedidos, pero a veces cuando el precio cambia relativamente grande, el pedido activará el mecanismo de límite de precios del intercambio, y puede llevar a que la orden de gran deslizamiento aún no se complete y se pierda la oportunidad comercial.

  • El plan 2

Utilizando la función precio de mercado de la bolsa, el pase de precio -1 en la FMZ es el precio de mercado.

  • El plan 3

Todavía usamos la lógica de negociación anterior y colocamos un pedido de límite, pero agregamos algo de detección a la lógica de negociación para tratar de resolver el problema causado por el retraso de los datos de posición. Después de que se coloca la orden, si la orden no se cancela, desaparece directamente en la lista de órdenes pendientes (la lista de órdenes pendientes desaparece de dos maneras posibles: 1 retira la orden, 2 ejecuta), detecta dicha situación y vuelve a colocar el monto de la orden. La cantidad de la última orden es la misma. En este momento, es necesario prestar atención a si los datos de posición se retrasan. Deja que el programa ingrese la lógica de espera para recuperar la información de posición. Incluso puedes continuar optimizando y aumentando el número de esperas de activación. Si supera un cierto número de veces, los datos de la interfaz de posición se retrasan. El problema es grave, deja que la lógica de transacción termine.

Diseño basado en el Plan 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")
}

Dirección del modelo:https://www.fmz.com/strategy/203258

La forma de llamar a la interfaz de la plantilla es como$.OpenLongy$.CoverLongEn elmainla función de arriba.

La plantilla es una versión beta, cualquier sugerencia es bienvenida, voy a seguir optimizando para hacer frente al problema de los retrasos en los datos de posición.


Relacionados

Más.