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

Explication détaillée de la stratégie de négociation de paires de devises numériques

Auteur:FMZ~Lydia, Créé à: 2024-07-08 11:41:23, mis à jour à: 2024-07-12 15:54:35

img

Introduction au projet

Récemment, j'ai vu BuOu's Quantitative Diary mentionner que vous pouvez utiliser des devises négativement corrélées pour sélectionner des devises et ouvrir des positions pour réaliser des profits en fonction des percées de différence de prix. Les devises numériques sont essentiellement corrélées positivement, et seulement quelques devises sont corrélées négativement, souvent avec des conditions de marché spéciales, telles que les conditions de marché indépendantes des pièces MEME, qui sont complètement différentes de la tendance du marché. Ces devises peuvent être sélectionnées et continuer longtemps après la percée. Cette méthode peut réaliser des profits dans des conditions de marché spécifiques. Cependant, la méthode la plus courante dans le domaine du trading quantitatif est d'utiliser la corrélation positive pour le trading en paires. Cet article présentera brièvement cette stratégie.

Le trading de paires de devises numériques est une stratégie de trading basée sur l'arbitrage statistique, qui consiste à acheter et à vendre simultanément deux crypto-monnaies hautement corrélées pour obtenir des bénéfices des écarts de prix.

Principe de stratégie

Les stratégies de trading en paire reposent sur la corrélation historique entre les prix de deux devises numériques. Lorsque les prix de deux devises montrent une forte corrélation, leurs tendances de prix sont généralement en synchronisation. Si le rapport de prix entre les deux dévie de manière significative à un certain moment, cela peut être considéré comme une anomalie temporaire et le prix aura tendance à revenir à des niveaux normaux. Le marché des devises numériques est très interconnecté.

Supposons que la monnaie A et la monnaie B aient une corrélation de prix élevée. À un certain moment, la valeur moyenne du rapport de prix A/B est 1. Si à un certain moment, le rapport de prix A/B dévie de plus de 0,001, c'est-à-dire de plus de 1,001, alors vous pouvez négocier de la manière suivante: ouvrir une position longue sur B et ouvrir une position courte sur A. Au contraire, lorsque le rapport de prix A/B est inférieur à 0,999: ouvrir une position longue sur A et ouvrir une position courte sur B.

La clé de la rentabilité réside dans les gains d'écart lorsque les prix dévient de la moyenne et reviennent à la normale.

Préparez les données

Importer la bibliothèque correspondante

Ces codes peuvent être utilisés directement. Il est préférable de télécharger Anancoda et de le déboguer dans Jupyer notebook. Il comprend des paquets pour l'analyse des données couramment utilisées directement.

import requests
from datetime import date,datetime
import time
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import requests, zipfile, io
%matplotlib inline

Obtenez toutes les paires commerciales en cours de négociation

Info = requests.get('https://fapi.binance.com/fapi/v1/exchangeInfo')
b_symbols = [s['symbol'] for s in Info.json()['symbols'] if s['contractType'] == 'PERPETUAL' and s['status'] == 'TRADING' and s['quoteAsset'] == 'USDT']
b_symbols = list(filter(lambda x: x[-4:] == 'USDT', [s.split('_')[0] for s in b_symbols]))
b_symbols = [x[:-4] for x in b_symbols]
print(b_symbols) # Get all trading pairs being traded

Télécharger la fonction K-line

La fonction principale de la fonction GetKlines est d'obtenir les données historiques de la ligne K du contrat perpétuel de la paire de négociation spécifiée de l'échange Binance et de stocker les données dans un Pandas DataFrame. Les données de la ligne K comprennent des informations telles que le prix d'ouverture, le prix le plus élevé, le prix le plus bas, le prix de clôture et le volume de négociation. Cette fois, nous utilisons principalement les données de prix de clôture.

def GetKlines(symbol='BTCUSDT',start='2020-8-10',end='2024-7-01',period='1h',base='fapi',v = 'v1'):
    Klines = []
    start_time = int(time.mktime(datetime.strptime(start, "%Y-%m-%d").timetuple()))*1000 + 8*60*60*1000
    end_time =  min(int(time.mktime(datetime.strptime(end, "%Y-%m-%d").timetuple()))*1000 + 8*60*60*1000,time.time()*1000)
    intervel_map = {'m':60*1000,'h':60*60*1000,'d':24*60*60*1000}
    while start_time < end_time:
        time.sleep(0.3)
        mid_time = start_time+1000*int(period[:-1])*intervel_map[period[-1]]
        url = 'https://'+base+'.binance.com/'+base+'/'+v+'/klines?symbol=%s&interval=%s&startTime=%s&endTime=%s&limit=1000'%(symbol,period,start_time,mid_time)
        res = requests.get(url)
        res_list = res.json()
        if type(res_list) == list and len(res_list) > 0:
            start_time = res_list[-1][0]+int(period[:-1])*intervel_map[period[-1]]
            Klines += res_list
        if type(res_list) == list and len(res_list) == 0:
            start_time = start_time+1000*int(period[:-1])*intervel_map[period[-1]]
        if mid_time >= end_time:
            break
    df = pd.DataFrame(Klines,columns=['time','open','high','low','close','amount','end_time','volume','count','buy_amount','buy_volume','null']).astype('float')
    df.index = pd.to_datetime(df.time,unit='ms')
    return df

Télécharger des données

Le volume de données est relativement important. Pour un téléchargement plus rapide, seules les données horaires de la ligne K des trois derniers mois sont obtenues. df_close contient les données de prix de clôture de toutes les devises.

start_date = '2024-04-01'
end_date   = '2024-07-05'
period = '1h'
df_dict = {}

for symbol in b_symbols:   
    print(symbol)
    if symbol in df_dict.keys():
        continue
    df_s = GetKlines(symbol=symbol+'USDT',start=start_date,end=end_date,period=period)
    if not df_s.empty:
        df_dict[symbol] = df_s
df_close = pd.DataFrame(index=pd.date_range(start=start_date, end=end_date, freq=period),columns=df_dict.keys())
for symbol in symbols:
    df_close[symbol] = df_dict[symbol].close
df_close = df_close.dropna(how='all')

Moteur de contre-test

Nous définissons un objet d'échange pour le backtest suivant.

class Exchange:
    def __init__(self, trade_symbols, fee=0.0002, initial_balance=10000):
        self.initial_balance = initial_balance #Initial assets
        self.fee = fee
        self.trade_symbols = trade_symbols
        self.account = {'USDT':{'realised_profit':0, 'unrealised_profit':0, 'total':initial_balance,
                                'fee':0, 'leverage':0, 'hold':0, 'long':0, 'short':0}}
        for symbol in trade_symbols:
            self.account[symbol] = {'amount':0, 'hold_price':0, 'value':0, 'price':0, 'realised_profit':0,'unrealised_profit':0,'fee':0}
            
    def Trade(self, symbol, direction, price, amount):
        cover_amount = 0 if direction*self.account[symbol]['amount'] >=0 else min(abs(self.account[symbol]['amount']), amount)
        open_amount = amount - cover_amount
        self.account['USDT']['realised_profit'] -= price*amount*self.fee #Deduction fee
        self.account['USDT']['fee'] += price*amount*self.fee
        self.account[symbol]['fee'] += price*amount*self.fee
        if cover_amount > 0: #Close the position first
            self.account['USDT']['realised_profit'] += -direction*(price - self.account[symbol]['hold_price'])*cover_amount  #profit
            self.account[symbol]['realised_profit'] += -direction*(price - self.account[symbol]['hold_price'])*cover_amount
            self.account[symbol]['amount'] -= -direction*cover_amount
            self.account[symbol]['hold_price'] = 0 if self.account[symbol]['amount'] == 0 else self.account[symbol]['hold_price']
        if open_amount > 0:
            total_cost = self.account[symbol]['hold_price']*direction*self.account[symbol]['amount'] + price*open_amount
            total_amount = direction*self.account[symbol]['amount']+open_amount
            
            self.account[symbol]['hold_price'] = total_cost/total_amount
            self.account[symbol]['amount'] += direction*open_amount      
    
    def Buy(self, symbol, price, amount):
        self.Trade(symbol, 1, price, amount)
        
    def Sell(self, symbol, price, amount):
        self.Trade(symbol, -1, price, amount)
        
    def Update(self, close_price): #Update the assets
        self.account['USDT']['unrealised_profit'] = 0
        self.account['USDT']['hold'] = 0
        self.account['USDT']['long'] = 0
        self.account['USDT']['short'] = 0
        for symbol in self.trade_symbols:
            if not np.isnan(close_price[symbol]):
                self.account[symbol]['unrealised_profit'] = (close_price[symbol] - self.account[symbol]['hold_price'])*self.account[symbol]['amount']
                self.account[symbol]['price'] = close_price[symbol]
                self.account[symbol]['value'] = self.account[symbol]['amount']*close_price[symbol]
                if self.account[symbol]['amount'] > 0:
                    self.account['USDT']['long'] += self.account[symbol]['value']
                if self.account[symbol]['amount'] < 0:
                    self.account['USDT']['short'] += self.account[symbol]['value']
                self.account['USDT']['hold'] += abs(self.account[symbol]['value'])
                self.account['USDT']['unrealised_profit'] += self.account[symbol]['unrealised_profit']
        self.account['USDT']['total'] = round(self.account['USDT']['realised_profit'] + self.initial_balance + self.account['USDT']['unrealised_profit'],6)
        self.account['USDT']['leverage'] = round(self.account['USDT']['hold']/self.account['USDT']['total'],3)

Analyse de la corrélation avec les monnaies filtrantes

Le calcul de la corrélation est une méthode de statistique utilisée pour mesurer la relation linéaire entre deux variables. La méthode de calcul de la corrélation la plus couramment utilisée est le coefficient de corrélation de Pearson. Voici le principe, la formule et la méthode de mise en œuvre du calcul de la corrélation.

  • 1 indique une corrélation positive parfaite, où les deux variables changent toujours en synchronisation. Quand une variable augmente, l'autre augmente également proportionnellement. Plus elle est proche de 1, plus la corrélation est forte.
  • -1 indique une corrélation négative parfaite, où les deux variables changent toujours dans des directions opposées.
  • 0 signifie qu'il n'y a pas de corrélation linéaire, il n'y a pas de relation linéaire droite entre les deux variables.

Le coefficient de corrélation de Pearson détermine la corrélation entre deux variables en calculant leur covariance et leur écart type.

img

dont:

  • imgest le coefficient de corrélation de Pearson entre les variables X et Y.
  • imgest la covariance de X et Y.
  • imgetimgsont les écarts types de X et Y respectivement.

Bien sûr, vous n'avez pas besoin de vous soucier trop de la façon dont il est calculé. Vous pouvez utiliser 1 ligne de code en Python pour calculer la corrélation de toutes les devises. La figure montre une carte thermique de corrélation. Le rouge représente la corrélation positive, le bleu représente la corrélation négative, et plus la couleur est foncée, plus la corrélation est forte. Vous pouvez voir que la majeure partie de la zone est rouge foncé, donc la corrélation positive des devises numériques est très forte.

img

import seaborn as sns
corr = df_close.corr()
plt.figure(figsize=(20, 20))
sns.heatmap(corr, annot=False, cmap='coolwarm', vmin=-1, vmax=1)
plt.title('Correlation Heatmap of Cryptocurrency Closing Prices', fontsize=20);

En fonction de la corrélation, les 20 principales paires de devises les plus corrélées sont sélectionnées.

MANA     SAND     0.996562
ICX      ZIL      0.996000
STORJ    FLOW     0.994193
FLOW     SXP      0.993861
STORJ    SXP      0.993822
IOTA     ZIL      0.993204
         SAND     0.993095
KAVA     SAND     0.992303
ZIL      SXP      0.992285
         SAND     0.992103
DYDX     ZIL      0.992053
DENT     REEF     0.991789
RDNT     MANTA    0.991690
STMX     STORJ    0.991222
BIGTIME  ACE      0.990987
RDNT     HOOK     0.990718
IOST     GAS      0.990643
ZIL      HOOK     0.990576
MATIC    FLOW     0.990564
MANTA    HOOK     0.990563

Le code correspondant est le suivant:

corr_pairs = corr.unstack()

# Remove self-correlation (i.e. values ​​on the diagonal)
corr_pairs = corr_pairs[corr_pairs != 1]

sorted_corr_pairs = corr_pairs.sort_values(kind="quicksort")

# Extract the top 20 most and least correlated currency pairs
most_correlated = sorted_corr_pairs.tail(40)[::-2]

print("The top 20 most correlated currency pairs are:")
print(most_correlated)

Vérification par contre-test

Le code de backtest spécifique est le suivant. La stratégie de démonstration observe principalement le rapport de prix de deux crypto-monnaies (IOTA et ZIL) et négocie en fonction des changements de ce rapport. Les étapes spécifiques sont les suivantes:

  1. Initialisation:
  • Définir les paires de négociation (pair_a = IOTA, pair_b = ZIL).
  • Créer un objet d'échangeeavec un solde initial de 10 000 $ et des frais de transaction de 0,02%.
  • Calculer le ratio de prix moyen initialavg.
  • Définir une valeur de transaction initialevalue = 1000.
  1. Iteration sur les données de prix:
  • Traversez les données de prix à chaque momentdf_close.
  • Calcule l'écart du ratio de prix courant par rapport à la moyennediff.
  • La valeur cible de la transaction est calculée sur la base de l'écartaim_valueLes opérations d'achat et de vente sont déterminées sur la base de la position du compte courant et de la situation des prix.
  • Si l'écart est trop grand, exécuter vendrepair_aet acheterpair_b operations.
  • Si l'écart est trop faible, acheterpair_aet vendrepair_bles opérations sont effectuées.
  1. Ajustez la moyenne:
  • Mise à jour du ratio de prix moyenavgpour refléter les derniers ratios de prix.
  1. Mettre à jour les comptes et enregistrements:
  • Mettre à jour les informations relatives à la position et au solde du compte de change.
  • Enregistrer l'état du compte à chaque étape (actifs totaux, actifs détenus, frais de transaction, positions longues et courtes) àres_list.
  1. Résultat de sortie:
  • Convertissezres_listà la trame de donnéesrespour une analyse et une présentation plus approfondies.
pair_a = 'IOTA'
pair_b = "ZIL"
e = Exchange([pair_a,pair_b], fee=0.0002, initial_balance=10000) #Exchange definition is placed in the comments section
res_list = []
index_list = []
avg = df_close[pair_a][0] / df_close[pair_b][0]
value = 1000
for idx, row in df_close.iterrows():
    diff = (row[pair_a] / row[pair_b] - avg)/avg
    aim_value = -value * diff / 0.01
    if -aim_value + e.account[pair_a]['amount']*row[pair_a] > 0.5*value:
        e.Sell(pair_a,row[pair_a],(-aim_value + e.account[pair_a]['amount']*row[pair_a])/row[pair_a])
        e.Buy(pair_b,row[pair_b],(-aim_value - e.account[pair_b]['amount']*row[pair_b])/row[pair_b])
    if -aim_value + e.account[pair_a]['amount']*row[pair_a]  < -0.5*value:
        e.Buy(pair_a, row[pair_a],(aim_value - e.account[pair_a]['amount']*row[pair_a])/row[pair_a])
        e.Sell(pair_b, row[pair_b],(aim_value + e.account[pair_b]['amount']*row[pair_b])/row[pair_b])
    avg = 0.99*avg + 0.01*row[pair_a] / row[pair_b]
    index_list.append(idx)
    e.Update(row)
    res_list.append([e.account['USDT']['total'],e.account['USDT']['hold'],
                         e.account['USDT']['fee'],e.account['USDT']['long'],e.account['USDT']['short']])
res = pd.DataFrame(data=res_list, columns=['total','hold', 'fee', 'long', 'short'],index = index_list)
res['total'].plot(grid=True);

Un total de 4 groupes de devises ont été testés en arrière-plan, et les résultats ont été idéaux. Le calcul actuel de la corrélation utilise des données futures, il n'est donc pas très précis. Cet article divise également les données en deux parties, basées sur le calcul précédent de la corrélation et le trading de backtest ultérieur. Les résultats sont un peu différents mais pas mauvais. Nous laissons à l'utilisateur de pratiquer et de vérifier.

img

Risques potentiels et moyens d'améliorer

Bien que la stratégie de négociation de paires puisse être rentable en théorie, il existe encore certains risques dans le fonctionnement réel: la corrélation entre les devises peut changer au fil du temps, ce qui entraîne l'échec de la stratégie; dans des conditions de marché extrêmes, les écarts de prix peuvent augmenter, entraînant de plus grandes pertes; la faible liquidité de certaines devises peut rendre les transactions difficiles à exécuter ou augmenter les coûts; et les frais générés par des transactions fréquentes peuvent éroder les bénéfices.

Pour réduire les risques et améliorer la stabilité des stratégies, les mesures d'amélioration suivantes peuvent être envisagées: recalculer régulièrement la corrélation entre les devises et ajuster les paires de négociation en temps opportun; fixer des points de stop loss et de profit pour contrôler la perte maximale d'une seule transaction; négocier plusieurs paires de devises en même temps pour diversifier les risques.

Conclusion

La stratégie de trading de paires de devises numériques réalise des bénéfices en profitant de la corrélation des prix des devises et en effectuant des opérations d'arbitrage lorsque les prix dévient. Cette stratégie a une grande faisabilité théorique. Un code source de stratégie de trading en direct simple basé sur cette stratégie sera publié plus tard. Si vous avez plus de questions ou avez besoin d'une discussion plus approfondie, n'hésitez pas à communiquer.


Plus de