avatar of 发明者量化-小小梦 发明者量化-小小梦
Suivre Messages privés
4
Suivre
1271
Abonnés

Quelques réflexions sur la logique du trading à terme de devises numériques

Créé le: 2020-06-01 09:52:45, Mis à jour le: 2023-10-08 19:41:25
comments   6
hits   2406

Quelques réflexions sur la logique du trading à terme de devises numériques

Quelques réflexions sur la logique du trading à terme de devises numériques

Scénario du problème

Pendant longtemps, le problème de retard des données de l’interface API de position de change de devises numériques m’a toujours dérangé. Je n’ai pas encore trouvé de solution appropriée, je vais donc reproduire ce problème. Habituellement, l’ordre de marché fourni par la bourse de contrats est en fait le prix de la contrepartie, donc parfois l’utilisation de ce soi-disant « ordre de marché » n’est pas fiable. Par conséquent, lorsque nous rédigeons des stratégies de trading de contrats à terme sur devises numériques, la plupart d’entre nous utilisons des ordres limités. Après avoir passé chaque commande, nous devons vérifier la position pour voir si la commande a été exécutée et si la position correspondante est maintenue. Le problème réside dans ces informations de position. Si l’ordre est exécuté, les données renvoyées par l’interface d’informations de position de la bourse (c’est-à-dire l’interface de bourse à laquelle nous avons effectivement accédé lorsque nous appelons exchange.GetPosition) doivent inclure les informations de position nouvellement ouvertes. Cependant, si les données renvoyées par l’échange sont des données anciennes, c’est-à-dire les informations de position avant que l’ordre qui vient d’être passé ne soit exécuté, alors il y aura un problème. La logique de trading peut penser que l’ordre n’a pas été exécuté et continuer à passer l’ordre. Cependant, l’interface de passation des commandes de la bourse ne connaît aucun retard. Au contraire, les transactions sont réalisées très rapidement et les commandes sont exécutées dès qu’elles sont passées. Cela entraînera une conséquence grave : la stratégie placera des ordres à plusieurs reprises lorsqu’une opération d’ouverture est déclenchée.

Expérience réelle

À cause de ce problème, j’ai vu une stratégie qui ouvrait une position longue complètement folle. Heureusement, le marché était en plein essor à ce moment-là et le profit flottant a dépassé une fois les 10 BTC. Heureusement, le marché est en plein boom, s’il avait chuté, le résultat aurait été prévisible.

Essayez de résoudre

  • Solution 1 La logique d’ordre de la stratégie peut être conçue pour ne placer qu’un seul ordre, et le prix de l’ordre est le prix de l’adversaire à ce moment-là plus un glissement plus important, de manière à prendre des ordres de l’adversaire d’une certaine profondeur. L’avantage de cette méthode est que vous ne passez qu’une seule commande et qu’elle n’est pas basée sur des informations de position. Cela peut éviter le problème des commandes en double, mais parfois, passer une commande lorsque le prix change considérablement peut déclencher le mécanisme de limite de prix de la bourse, et il est possible que même avec un glissement important, la commande ne soit toujours pas exécutée, manquant ainsi l’opportunité .

  • Solution 2 Utilisez la fonction d’ordre de marché de la bourse et transmettez -1 sur FMZ comme prix, ce qui correspond à un ordre de marché. Actuellement, l’interface des contrats à terme OKEX a été mise à niveau pour prendre en charge les ordres de marché réels.

  • Solution 3 Nous utilisons toujours la logique de trading précédente et passons des commandes à l’aide d’ordres limités, mais nous ajoutons quelques détections à la logique de trading pour tenter de résoudre le problème causé par le retard des données de position. Vérifiez si la commande disparaît de la liste des commandes en attente sans être annulée après avoir passé la commande (il existe deux possibilités de disparition de la liste des commandes en attente : 1 annulation et 2 exécution). Si une telle situation est détectée et que la quantité de commande et la commande le volume est le même que la dernière fois. À ce stade, vous devez faire attention à ce que les données de position ne soient pas retardées. Laissez le programme entrer dans la logique d’attente pour réacquérir les informations de position. Vous pouvez même continuer à optimiser et à augmenter le nombre de déclenchement des attentes. Si ce nombre dépasse un certain nombre, cela signifie que les données de l’interface de position sont retardées. Le problème est grave et la logique de cette transaction est terminée.

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

Adresse du modèle : https://www.fmz.com/strategy/203258

La manière d’appeler l’interface du modèle est la même que dans la fonction principale ci-dessus$.OpenLong$.CoverLong。 Le modèle est une version bêta. Les suggestions et commentaires sont les bienvenus. Nous continuerons à l’optimiser pour résoudre le problème de retard des données de position.