Un poco de reflexión sobre la lógica del comercio de futuros de monedas digitales

El autor:Un sueño pequeño., Creado: 2020-06-01 09:52:45, Actualizado: 2023-10-08 19:41:25

img

Un poco de reflexión sobre la lógica del comercio de futuros de monedas digitales

El escenario del problema

长久以来,数字货币交易所持仓API接口的数据延迟问题总是困扰着我。一直没有找到合适的处理方式,这个问题的场景我来复现下。通常合约交易所提供的市价单其实为对手价,所以有时候用这个所谓的“市价单”有些不靠谱。因此我们在写数字货币期货交易策略时,大部分用的是限价单。在每次下单后,我们要检查持仓,看看下单是不是成交了,并且持有了对应的仓位。问题就出在这个持仓信息上,如果订单成交了,交易所持仓信息接口(就是我们调用exchange.GetPosition时底层实际去访问的交易所接口)返回的数据应当是包含新开仓持仓信息的,但是交易所返回的数据如果是旧数据,即刚才下单的订单成交前的持仓信息,这样就出问题了。交易逻辑可能认为订单没有成交,继续下单。但是交易所下单接口并不延迟,反而成交很快,下单就成交。这样会造成一种严重的后果就是策略在一次触发开仓的操作时会不停的重复下单。

La experiencia real

Por este problema, vi una estrategia loca de abrir múltiples posiciones, afortunadamente debido a la caída del mercado, que floreció una vez por encima de 10 BTC.

Tratar de solucionarlo

  • Opción 1 Se puede diseñar una estrategia para que la lógica de la orden sea sólo la siguiente orden, el precio de la orden se suma a un precio de cambio más grande para el precio del rival en el momento de la transacción, para comer la orden de la competencia de cierta profundidad. La ventaja de hacer esto es que sólo la orden de la próxima vez, y no se juzga sobre la base de la información de la posesión. Esto evita el problema de la repetición de la orden, pero a veces puede que el precio de cambio sea mayor, la orden se dispara el mecanismo de precios de los intercambios, y es posible que el precio de la operación aún no se haya completado, perdiendo la oportunidad.

  • Opción 2 Con la función de la lista de precios del mercado de los intercambios, la transmisión de precios en FMZ-1 es la lista de precios del mercado, y actualmente la interfaz de futuros de OKEX se ha actualizado para admitir una lista de precios real.

  • Opción 3 Seguimos usando la lógica de transacción anterior, usando el pedido de pedido de precio limitado, pero agregamos algunas pruebas en la lógica de transacción para tratar de resolver el problema causado por el retraso de los datos de posición. La detección de si el pedido de pedido desaparece directamente en la lista de suspensión en caso de no ser revocado (dos posibilidades de desaparecer de la lista de suspensión: 1 retiro, 2 entregas), detecta este tipo de situaciones y vuelve a realizar el mismo pedido que la última vez, en este momento hay que tener en cuenta si el pedido de pedido es el mismo, deja que el programa entre en la lógica de espera, vuelve a obtener la información de almacenamiento, e incluso puede continuar optimizando, aumentando el número de espera de disparos, más de un cierto número de veces, indica que el problema de retraso de datos de la interfaz de almacenamiento es grave, y deja que la lógica de transacción termine.

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")
}

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

La forma de llamar a la interfaz de la plantilla es como en la función principal.$.OpenLong$.CoverLong¿Qué es esto? La plantilla es una versión de prueba, por lo que es bienvenido a enviar sugerencias, y continuará optimizándose para abordar el problema del retraso de los datos de almacenamiento.


Relacionados

Más.

excmLa mejor manera de hacerlo debería ser con ws, ordenando que el servidor te avise de inmediato cuando hay una actualización, en lugar de preguntarte una vez.

Bot de XueqiuEncuentro este problema, mi solución es registrar la tenencia antes de la orden, luego registrar el id después de la orden ioc, el ciclo obtiene el estado de la orden, si es transacción / transacción parcial, el ciclo se compara con la tenencia actual y la tenencia registrada, cuando los dos valores son diferentes salta del ciclo.

Un sueño pequeño.En la actualidad, la mayoría de los usuarios de la red social han tenido problemas con sus sistemas operativos.

excmSi la conexión se interrumpe en un momento crítico, no hay ningún problema en establecer un buen mecanismo de reconexión; por supuesto, hay una solución completa, que usted tiene que tocar.

Un sueño pequeño.Sin embargo, cuando la interfaz WS también es poco confiable, esto causa más problemas que el protocolo REST.

Un sueño pequeño.Muchas gracias, sigue aprendiendo.