2
Suivre
319
Abonnés

Une brève analyse des stratégies d'arbitrage : comment saisir des opportunités à faible risque avec des délais courts

Créé le: 2025-03-07 14:11:55, Mis à jour le: 2025-03-10 17:38:06
comments   0
hits   1389

Une brève analyse des stratégies d’arbitrage : comment saisir des opportunités à faible risque avec des délais courts

L’inspiration de cette stratégie vient de l’article d’opportunité de l’auteur de Zhihu “Dream Dealer” - “TRUMP et MELANIA modèle d’arbitrage de corrélation à faible risque”. L’article explore la corrélation des prix entre deux contrats lancés sur BN (TRUMP et MELANIA) et utilise le délai subtil entre les deux pour tenter de capturer les fluctuations du marché à court terme et de réaliser un arbitrage à faible risque. Ensuite, nous expliquerons les principes de cette stratégie, la logique d’implémentation du code et explorerons les directions d’optimisation possibles.

Il faut être à l’avanceAvisLe problème est que cette stratégie équivaut à un travail de trading manuel. Elle n’offre une certaine opportunité de profit qu’après avoir trouvé deux paires de trading appropriées, et la durée de vie du profit de la paire de trading peut être courte. Lorsqu’il s’avère qu’il n’y a aucune opportunité de profit, il est nécessaire d’arrêter la stratégie à temps pour éviter une baisse des bénéfices, voire une perte.


1. Principes stratégiques et pertinence du marché

1.1 Contexte stratégique

Les contrats TRUMP et MELANIA sont émis par la même équipe émettrice et disposent des mêmes fonds de contrôle, de sorte que leurs tendances de prix sont la plupart du temps très synchronisées. Cependant, en raison de facteurs tels que la conception du contrat ou l’exécution du marché, le prix de MELANIA a tendance à être inférieur de 1 à 2 secondes à celui de TRUMP. Ce petit délai offre aux arbitragistes la possibilité de capturer les différences de prix et d’effectuer des transactions de copie à haute fréquence. En d’autres termes, lorsque TRUMP fluctue rapidement, MELANIA a tendance à suivre le mouvement très rapidement. En profitant de ce délai, les transactions peuvent être réalisées avec un risque moindre.

Une brève analyse des stratégies d’arbitrage : comment saisir des opportunités à faible risque avec des délais courts

Une brève analyse des stratégies d’arbitrage : comment saisir des opportunités à faible risque avec des délais courts

1.2 Prévalence sur le marché des crypto-monnaies

Des phénomènes de corrélation similaires ne sont pas rares sur le marché des crypto-monnaies :

  • Différents contrats ou dérivés du même projet:En raison des mêmes actifs sous-jacents ou de la même expérience d’équipe, les prix des différents produits présentent souvent des liens étroits.
  • Arbitrage entre bourses:Le même actif sur différentes bourses peut avoir de légères différences de prix en raison de différences de liquidité et de mécanismes de correspondance.
  • Stablecoins et produits indexés sur la monnaie fiduciaire:Ces produits présentent souvent des écarts de taux de change attendus, et les arbitragistes peuvent profiter de légères fluctuations.

Cette corrélation fournit aux traders à haute fréquence et aux arbitragistes des signaux de trading stables et des opportunités opérationnelles à faible risque, mais elle nécessite également que les stratégies de trading soient très sensibles aux changements subtils du marché et soient capables de réagir en temps réel.


2. Explication détaillée de la logique du code

Le code se compose principalement de plusieurs parties, chaque module correspond aux étapes clés de la stratégie d’arbitrage.

2.1 Description des fonctions auxiliaires

Obtenir des informations sur la position

function GetPosition(pair){
    let pos = exchange.GetPosition(pair)
    if(pos.length == 0){
        return {amount:0, price:0, profit:0}
    }else if(pos.length > 1){
        throw '不支持双向持仓'
    }else if(pos.length == 1){
        return {amount:pos[0].Type == 0 ? pos[0].Amount : -pos[0].Amount, price:pos[0].Price, profit:pos[0].Profit}
    }else{
        Log('未获取仓位数据')
        return null
    }
}
  • Fonctionnalités:Cette fonction est utilisée pour obtenir uniformément les informations de position de la paire de trading spécifiée et convertir les positions longues et courtes en nombres positifs et négatifs.
  • La logique: Si la position est vide, renvoie la position zéro par défaut ; s’il y a plus d’un ordre, une erreur est signalée (pour garantir une position à sens unique) ; sinon, renvoie la direction, le prix et le profit et la perte flottants de la position actuelle.

Initialiser le compte

function InitAccount(){
    let account = _C(exchange.GetAccount)
    let total_eq = account.Equity

    let init_eq = 0
    if(!_G('init_eq')){
        init_eq = total_eq
        _G('init_eq', total_eq)
    }else{
        init_eq = _G('init_eq')
    }

    return init_eq
}
  • Fonctionnalités:Cette fonction est utilisée pour initialiser et enregistrer les capitaux propres du compte comme base pour les calculs ultérieurs de profits et pertes.

Annuler la commande en attente

function CancelPendingOrders() {
    orders = exchange.GetOrders();  // 获取订单
    for (let order of orders) {
        if (order.Status == ORDER_STATE_PENDING) {  // 只取消未完成的订单
            exchange.CancelOrder(order.Id);  // 取消挂单
        }
    }
}
  • Fonctionnalités:Avant de passer une commande, assurez-vous d’annuler toutes les commandes non terminées précédemment afin d’éviter les conflits de commandes ou les commandes en double.

2.2 Logique de transaction principale

La fonction principale utilise une boucle infinie pour exécuter en continu les étapes suivantes :

  1. Acquisition de données et calcul de marché
    Chaque cycle commence parexchange.GetRecords Obtenez les données de marché de Pair_A et Pair_B respectivement.

    • Formule de calcul : [ ratio = \frac{Close{A} - Open{A}}{Open{A}} - \frac{Close{B} - Open{B}}{Open{B}} ] En comparant la hausse et la baisse des deux, nous pouvons déterminer s’il existe une différence de prix anormale. Lorsque la différence de prix dépasse le diffLevel prédéfini, la condition d’ouverture est déclenchée.
  2. Déterminer les conditions d’ouverture et passer commande
    Lorsqu’il n’y a pas de position actuelle (position_B.amount == 0) et que le trading est autorisé (afterTrade==1) :

    • Si le ratio est supérieur à diffLevel, on pense que le marché est sur le point d’augmenter, donc un ordre d’achat est émis pour Pair_B (achat d’une position longue).
    • Si le ratio est inférieur à -diffLevel, on considère que le marché est sur le point de baisser et un ordre de vente est émis (une position courte est ouverte). Avant de passer une commande, la fonction d’annulation de commande sera appelée pour garantir que le statut de commande actuel est effacé.
  3. Logique stop-profit et stop-loss
    Une fois la position établie, la stratégie définira les ordres take-profit et stop-loss correspondants en fonction de la direction de la position :

    • Position longue (achat): Définissez le prix du take profit sur le prix de la position multiplié par (1 + stopProfitLevel) et le prix du stop loss sur le prix de la position multiplié par (1 - stopLossLevel).
    • Position courte (vente):Le prix de take profit est défini sur le prix de la position multiplié par (1 - stopProfitLevel), et le prix stop loss est défini sur le prix de la position multiplié par (1 + stopLossLevel). Le système surveillera le prix du marché en temps réel. Une fois les conditions de take-profit ou de stop-loss déclenchées, l’ordre en attente d’origine sera annulé et un ordre sera placé pour clôturer la position.
  4. Statistiques de profit et enregistrements de journal après la fermeture d’une position
    Après la fermeture de chaque position, le système obtiendra les variations de la valeur nette du compte et comptera le nombre de bénéfices, le nombre de pertes et le montant cumulé des bénéfices/pertes.
    Dans le même temps, des tableaux et des graphiques sont utilisés pour afficher les informations de position actuelles, les statistiques de transaction et les retards de cycle en temps réel, ce qui est pratique pour l’analyse ultérieure des effets de la stratégie.


3. Optimisation de la stratégie et méthodes d’expansion

Bien que cette stratégie exploite le délai subtil entre deux contrats hautement corrélés, de nombreux domaines peuvent encore être améliorés :

3.1 Optimisation des paramètres et ajustement dynamique

  • Réglage du seuil:Des paramètres tels que diffLevel, stopProfitLevel et stopLossLevel peuvent devoir être ajustés dans différents environnements de marché. Ces paramètres peuvent être optimisés automatiquement grâce à des tests rétrospectifs de données historiques ou en ajustant dynamiquement les modèles en temps réel (par exemple, des algorithmes d’apprentissage automatique).
  • Gestion des positions:La stratégie actuelle utilise un Trade_Number fixe pour ouvrir une position. À l’avenir, nous pouvons envisager d’introduire une gestion de position dynamique ou un mécanisme d’ouverture de positions par lots et de prise de bénéfices progressive pour réduire le risque d’une transaction unique.

3.2 Filtrage des signaux de trading

  • Signal multifactoriel:Le calcul du ratio basé uniquement sur les augmentations et les diminutions de prix peut être affecté par le bruit. Vous pouvez envisager d’introduire le volume des transactions, la profondeur du carnet d’ordres et des indicateurs techniques (tels que RSI, MACD, etc.) pour filtrer davantage les faux signaux.
  • Indemnisation de retard:Étant donné que MELANIA a un retard de 1 à 2 secondes, le développement d’un mécanisme de synchronisation temporelle et de prédiction du signal plus précis contribuera à améliorer la précision du timing d’entrée.

3.3 Robustesse du système et contrôle des risques

  • Gestion des erreurs:Ajoutez la gestion et la journalisation des exceptions pour garantir une réponse rapide en cas de retards sur le réseau ou d’anomalies de l’interface d’échange, et éviter les pertes inattendues causées par des pannes du système.
  • Stratégie de contrôle des risques:Combinez la gestion du capital et le contrôle maximal des drawdowns pour définir une limite de perte quotidienne ou sur transaction unique afin d’éviter des pertes consécutives dans des environnements de marché extrêmes.

3.4 Optimisation du code et de l’architecture

  • Traitement asynchrone:Actuellement, la boucle de stratégie est exécutée toutes les 100 millisecondes. Grâce au traitement asynchrone et à l’optimisation multithread, le risque de retard et de blocage de l’exécution peut être réduit.
  • Backtesting et simulation de stratégie:Introduisez un système de backtesting complet et un environnement de trading simulé en temps réel pour vérifier les performances des stratégies dans différentes conditions de marché et aider les stratégies à fonctionner de manière plus stable dans le trading réel.

IV. Conclusion

Cet article présente en détail les principes de base et le code d’implémentation d’une stratégie d’arbitrage de corrélation de contrats à court terme. De l’exploitation de la différence entre les hausses et les baisses de prix à la capture d’opportunités d’entrée, en passant par la définition de stop-profit et de stop-loss pour la gestion des positions, cette stratégie tire pleinement parti de la forte corrélation entre les actifs du marché des crypto-monnaies. Dans le même temps, nous avons également proposé un certain nombre de suggestions d’optimisation, notamment l’ajustement dynamique des paramètres, le filtrage du signal, la robustesse du système et l’optimisation du code, afin d’améliorer encore la stabilité et la rentabilité de la stratégie dans les applications en temps réel.

Bien que la stratégie soit particulièrement inspirée et simple à mettre en œuvre, toute opération d’arbitrage doit être traitée avec prudence sur le marché des crypto-monnaies à haute fréquence et volatil. J’espère que cet article pourra fournir une référence précieuse et une inspiration aux amis passionnés par le trading quantitatif et les stratégies d’arbitrage.


Remarque : l’environnement de test de stratégie est la simulation de trading OKX, et les détails spécifiques peuvent être modifiés pour différentes bourses

function GetPosition(pair){
    let pos = exchange.GetPosition(pair)
    if(pos.length == 0){
        return {amount:0, price:0, profit:0}
    }else if(pos.length > 1){
        throw '不支持双向持仓'
    }else if(pos.length == 1){
        return {amount:pos[0].Type == 0 ? pos[0].Amount : -pos[0].Amount, price:pos[0].Price, profit:pos[0].Profit}
    }else{
        Log('未获取仓位数据')
        return null
    }
}

function InitAccount(){
    let account = _C(exchange.GetAccount)
    let total_eq = account.Equity

    let init_eq = 0
    if(!_G('init_eq')){
        init_eq = total_eq
        _G('init_eq', total_eq)
    }else{
        init_eq = _G('init_eq')
    }

    return init_eq
}

function CancelPendingOrders() {
    orders = exchange.GetOrders();  // 获取订单
    for (let order of orders) {
        if (order.Status == ORDER_STATE_PENDING) {  // 只取消未完成的订单
            exchange.CancelOrder(order.Id);  // 取消挂单
        }
    }
}

var pair_a = Pair_A + "_USDT.swap";
var pair_b = Pair_B + "_USDT.swap";


function main() {
    exchange.IO('simulate', true);
    LogReset(0);
    Log('策略开始运行')

    var precision = exchange.GetMarkets();
    var ratio = 0

    var takeProfitOrderId = null;
    var stopLossOrderId = null;
    var successCount = 0;
    var lossCount = 0;
    var winMoney = 0;
    var failMoney = 0;
    var afterTrade = 1;

    var initEq = InitAccount();

    var curEq = initEq

    var pricePrecision = precision[pair_b].PricePrecision;

    while (true) {
        try{
            let startLoopTime = Date.now();
            let position_B = GetPosition(pair_b);
            let new_r_pairB = exchange.GetRecords(pair_b, 1).slice(-1)[0];

            if (!new_r_pairB || !position_B) {
                Log('跳过当前循环');
                continue;
            }
            
            // 合并交易条件:检查是否可以开仓并进行交易
            if (afterTrade == 1 && position_B.amount == 0) {
                
                let new_r_pairA = exchange.GetRecords(pair_a, 1).slice(-1)[0];
                if (!new_r_pairA ) {
                    Log('跳过当前循环');
                    continue;
                }
                
                ratio = (new_r_pairA.Close - new_r_pairA.Open) / new_r_pairA.Open - (new_r_pairB.Close - new_r_pairB.Open) / new_r_pairB.Open;

                if (ratio > diffLevel) {
                    CancelPendingOrders();
                    Log('实时ratio:', ratio, '买入:', pair_b, position_B.amount);
                    exchange.CreateOrder(pair_b, "buy", -1, Trade_Number);
                    afterTrade = 0;
                } else if (ratio < -diffLevel) {
                    CancelPendingOrders();
                    Log('实时ratio:', ratio, '卖出:', pair_b, position_B.amount);
                    exchange.CreateOrder(pair_b, "sell", -1, Trade_Number);
                    afterTrade = 0;
                }            
            }

            

            // 判断止盈止损
            if (position_B.amount > 0 && takeProfitOrderId == null && stopLossOrderId == null && afterTrade == 0) {
                Log('多仓持仓价格:', position_B.price, '止盈价格:', position_B.price * (1 + stopProfitLevel), '止损价格:', position_B.price * (1 - stopLossLevel));
                takeProfitOrderId = exchange.CreateOrder(pair_b, "closebuy", position_B.price * (1 + stopProfitLevel), position_B.amount);
                Log('止盈订单:', takeProfitOrderId);
            }

            if (position_B.amount > 0 && takeProfitOrderId != null && stopLossOrderId == null && new_r_pairB.Close < position_B.price * (1 - stopLossLevel) && afterTrade == 0) {
                CancelPendingOrders();
                takeProfitOrderId = null
                Log('多仓止损');
                stopLossOrderId = exchange.CreateOrder(pair_b, "closebuy", -1, position_B.amount);
                Log('多仓止损订单:', stopLossOrderId);
            }

            if (position_B.amount < 0 && takeProfitOrderId == null && stopLossOrderId == null && afterTrade == 0) {
                Log('空仓持仓价格:', position_B.price, '止盈价格:', position_B.price * (1 - stopProfitLevel), '止损价格:', position_B.price * (1 + stopLossLevel));
                takeProfitOrderId = exchange.CreateOrder(pair_b, "closesell", position_B.price * (1 - stopProfitLevel), -position_B.amount);
                Log('止盈订单:', takeProfitOrderId, '当前价格:', new_r_pairB.Close );
            }

            if (position_B.amount < 0 && takeProfitOrderId != null && stopLossOrderId == null && new_r_pairB.Close > position_B.price * (1 + stopLossLevel) && afterTrade == 0) {
                CancelPendingOrders();
                takeProfitOrderId = null
                Log('空仓止损');
                stopLossOrderId = exchange.CreateOrder(pair_b, "closesell", -1, -position_B.amount);
                Log('空仓止损订单:', stopLossOrderId);
            }


            // 平市价单未完成
            if (takeProfitOrderId == null && stopLossOrderId != null && afterTrade == 0) {
                
                let stoplosspos = GetPosition(pair_b)
                if(stoplosspos.amount > 0){
                    Log('平多仓市价单未完成')
                    exchange.CreateOrder(pair_b, 'closebuy', -1, stoplosspos.amount)
                }
                if(stoplosspos.amount < 0){
                    Log('平空仓市价单未完成')
                    exchange.CreateOrder(pair_b, 'closesell', -1, -stoplosspos.amount)
                }
            }

            // 未平仓完毕
            if (Math.abs(position_B.amount) < Trade_Number && Math.abs(position_B.amount) > 0 && afterTrade == 0){
                Log('未平仓完毕')
                if(position_B.amount > 0){
                    exchange.CreateOrder(pair_b, 'closebuy', -1, position_B.amount)
                }else{
                    exchange.CreateOrder(pair_b, 'closesell', -1, -position_B.amount)
                }
            }

            // 计算盈亏
            if (position_B.amount == 0 && afterTrade == 0) {
                if (stopLossOrderId != null || takeProfitOrderId != null) {
                    stopLossOrderId = null;
                    takeProfitOrderId = null;

                    let afterEquity = exchange.GetAccount().Equity;
                    let curAmount = afterEquity - curEq;

                    curEq = afterEquity

                    if (curAmount > 0) {
                        successCount += 1;
                        winMoney += curAmount;
                        Log('盈利金额:', curAmount);
                    } else {
                        lossCount += 1;
                        failMoney += curAmount;
                        Log('亏损金额:', curAmount);
                    }
                    afterTrade = 1;
                }
            }

            if (startLoopTime % 10 == 0) {  // 每 10 次循环记录一次
                let curEquity = exchange.GetAccount().Equity

                // 输出交易信息表
                let table = {
                    type: "table",
                    title: "交易信息",
                    cols: [
                        "初始权益", "当前权益", Pair_B + "仓位", Pair_B + "持仓价", Pair_B + "收益", Pair_B + "价格", 
                        "盈利次数", "盈利金额", "亏损次数", "亏损金额", "胜率", "盈亏比"
                    ],
                    rows: [
                        [
                            _N(_G('init_eq'), 2),  // 初始权益
                            _N(curEquity, 2),  // 当前权益
                            _N(position_B.amount, 1),  // Pair B 仓位
                            _N(position_B.price, pricePrecision),  // Pair B 持仓价
                            _N(position_B.profit, 1),  // Pair B 收益
                            _N(new_r_pairB.Close, pricePrecision),  // Pair B 价格
                            _N(successCount, 0),  // 盈利次数
                            _N(winMoney, 2),  // 盈利金额
                            _N(lossCount, 0),  // 亏损次数
                            _N(failMoney, 2),  // 亏损金额
                            _N(successCount + lossCount === 0 ? 0 : successCount / (successCount + lossCount), 2),  // 胜率
                            _N(failMoney === 0 ? 0 : winMoney / failMoney * -1, 2)  // 盈亏比
                        ]
                    ]
                };

                $.PlotMultLine("ratio plot", "幅度变化差值", ratio, startLoopTime);
                $.PlotMultHLine("ratio plot", diffLevel, "差价上限", "red", "ShortDot");
                $.PlotMultHLine("ratio plot", -diffLevel, "差价下限", "blue", "ShortDot");

                LogStatus("`" + JSON.stringify(table) + "`");
                LogProfit(curEquity - initEq, '&')
            }
        }catch(e){
            Log('策略出现错误:', e)
        }

        
        Sleep(200);
    }
}