avatar of 发明者量化-小小梦 发明者量化-小小梦
Seguir Mensajes Privados
4
Seguir
1271
Seguidores

Algunas reflexiones sobre la lógica del trading de futuros de monedas digitales

Creado el: 2020-06-01 09:52:45, Actualizado el: 2023-10-08 19:41:25
comments   6
hits   2406

Algunas reflexiones sobre la lógica del trading de futuros de monedas digitales

Algunas reflexiones sobre la lógica del trading de futuros de monedas digitales

Escenario del problema

Durante mucho tiempo, el problema del retraso de datos de la interfaz API de posición de cambio de moneda digital siempre me ha molestado. Todavía no he encontrado una solución adecuada, así que reproduciré este problema. Generalmente la orden de mercado proporcionada por el intercambio de contratos es en realidad el precio de la contraparte, por lo que a veces el uso de esta denominada “orden de mercado” no es confiable. Por lo tanto, cuando escribimos estrategias de negociación de futuros de monedas digitales, la mayoría de nosotros utilizamos órdenes limitadas. Después de colocar cada orden, debemos verificar la posición para ver si la orden se ha ejecutado y si la posición correspondiente se mantiene. El problema radica en esta información de posición. Si se ejecuta la orden, los datos devueltos por la interfaz de información de posición de la bolsa (es decir, la interfaz de bolsa a la que realmente se accede cuando llamamos a exchange.GetPosition) deberían incluir la información de la posición recién abierta. Sin embargo, si los datos devueltos por el intercambio son datos antiguos, es decir, la información de posición antes de que se ejecutara la orden recién colocada, entonces habrá un problema. La lógica comercial puede pensar que la orden no se ha ejecutado y continuar colocando la orden. Sin embargo, la interfaz de colocación de órdenes de la bolsa no sufre retrasos, sino que las transacciones se completan muy rápidamente y las órdenes se ejecutan tan pronto como se colocan. Esto tendrá como consecuencia grave que la estrategia coloque órdenes repetidamente cuando se active una operación de apertura.

Experiencia real

Debido a este problema, vi una estrategia que abrió una posición larga loca. Afortunadamente, el mercado estaba en auge en ese momento y la ganancia flotante superó los 10 BTC. Afortunadamente, el mercado está en auge; si se hubiera desplomado, el resultado sería predecible.

Intentar resolverlo

  • Solución 1 La lógica de órdenes de la estrategia se puede diseñar para colocar solo una orden, y el precio de la orden es el precio del oponente en ese momento más un deslizamiento mayor, para así tomar órdenes del oponente de cierta profundidad. La ventaja de hacer esto es que solo realizas un pedido una vez y no se basa en información de posición. Esto puede evitar el problema de órdenes duplicadas, pero a veces colocar una orden cuando el precio cambia mucho puede activar el mecanismo de límite de precio del intercambio, y es posible que incluso con un gran deslizamiento, la orden aún no se ejecute, perdiendo así la oportunidad. .

  • Solución 2 Utilice la función de orden de mercado del exchange y pase -1 en FMZ como precio, que es una orden de mercado. Actualmente, la interfaz de futuros de OKEX se ha actualizado para admitir órdenes de mercado reales.

  • Solución 3 Todavía utilizamos la lógica comercial anterior y colocamos órdenes utilizando órdenes limitadas, pero agregamos algunas detecciones a la lógica comercial para intentar resolver el problema causado por el retraso en los datos de posición. Compruebe si el pedido desaparece de la lista de pedidos pendientes sin cancelarse después de realizar el pedido (hay dos posibilidades de desaparición de la lista de pedidos pendientes: 1 cancelación y 2 cumplimiento). Si se detecta tal situación y la cantidad del pedido y el pedido El volumen es el mismo que la última vez. En este momento, debe prestar atención a si los datos de posición están retrasados. Deje que el programa ingrese a la lógica de espera para volver a adquirir la información de posición. Incluso puede continuar optimizando y aumentando el número. de activación de esperas. Si supera un número determinado, significa que los datos de la interfaz de posición se retrasan. El problema es grave y se termina la lógica de esta transacción.

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

Dirección de la plantilla: https://www.fmz.com/strategy/203258

La forma de llamar a la interfaz de plantilla es la misma que en la función principal anterior.$.OpenLong$.CoverLong。 La plantilla es una versión beta. Se aceptan sugerencias y comentarios. Continuaremos optimizándola para solucionar el problema del retraso en los datos de posición.