Les ressources ont été chargées... Je charge...

Un peu de réflexion sur la logique des échanges de devises numériques

Auteur:L'inventeur de la quantification - un petit rêve, Créé: 2020-06-01 09:52:45, Mis à jour: 2023-10-08 19:41:25

img

Un peu de réflexion sur la logique des échanges de devises numériques

Le scénario du problème

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

Une expérience réelle

En raison de ce problème, j'ai vu une stratégie folle d'ouverture de plusieurs positions, heureusement, le marché a explosé et a flotté au-dessus de 10 BTC une fois.

Essayez de résoudre

  • Résolution 1 On peut concevoir une stratégie de souscription logique pour que l'ordre soit uniquement le prochain, le prix du souscription plus un prix plus élevé pour le prix de l'opposant en cours d'exécution, et de manger un ordre d'opposant d'une certaine profondeur. L'avantage de cela est que l'ordre est seulement le prochain, et ne juge pas sur la base de l'information de stock.

  • Option 2 L'interface OKEX est actuellement mise à niveau pour prendre en charge les véritables listes de prix.

  • Le projet 3 Nous continuons à utiliser la logique de transaction précédente, en utilisant la commande à prix limité, mais nous ajoutons quelques tests à la logique de transaction pour essayer de résoudre le problème causé par le retard de données de position. Détecter si les commandes post-commande ont disparu directement dans la liste de pendentif sans être annulées (deux possibilités de disparition de la liste de pendentif: 1 retrait, 2 fractions de livraison), détecter de telles situations et commander à nouveau la même quantité que la dernière fois, ce qui nécessite une attention particulière pour savoir si les données de stockage sont retardées, laisser le programme entrer dans la logique d'attente, récupérer les informations de stockage, ou même continuer à optimiser, augmenter le nombre de fois de mise en attente déclenchée, plus d'un certain nombre de fois indique que le problème de retard de données d'interface de stockage est grave, ce qui met fin à la logique de transaction.

Conception basée sur le schéma 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")
}

L'adresse du modèle:https://www.fmz.com/strategy/203258

La façon d'appeler l'interface du modèle est la même que celle de la fonction main ci-dessus.$.OpenLong$.CoverLongJe ne sais pas. Le modèle est en version de test, les suggestions sont les bienvenues et seront continuellement optimisées pour résoudre les problèmes de latence des données de stockage.


Relationnée

Plus de

excmLa meilleure façon de faire est d'utiliser les commandes avec les commandes avec les commandes avec les commandes avec les commandes avec les commandes avec les commandes.

Bot de XueqiuJ'ai rencontré ce problème, ma solution est d'enregistrer les positions avant la commande, puis après la commande ioc, enregistrer l'id, le cycle obtient l'état de l'ordre, s'il s'agit d'une transaction/partielle transaction, le cycle compare le stock actuel et le stock enregistré, et saute du cycle lorsque ces deux valeurs sont différentes.

L'inventeur de la quantification - un petit rêveIl y a eu des WS instables, des pannes, des avantages et des inconvénients.

excmNous n'avons pas de problème, même si nous sommes déconnectés à un moment critique, nous n'avons pas de problème pour établir un bon mécanisme de liaison; bien sûr, il y a aussi une solution complète, que vous devez trouver vous-même

L'inventeur de la quantification - un petit rêveCependant, les interfaces WS sont parfois moins fiables, ce qui est encore plus gênant que le protocole REST.

L'inventeur de la quantification - un petit rêveMerci beaucoup, continuez à apprendre.