
J’ai récemment vu le journal quantitatif de Buu qui mentionnait que vous pouvez utiliser des devises corrélées négativement pour sélectionner des devises et ouvrir des positions pour réaliser des bénéfices en fonction des percées de différence de prix. Les monnaies numériques sont fondamentalement corrélées positivement, et seules quelques monnaies sont corrélées négativement. Elles ont souvent des conditions de marché particulières, telles que les conditions de marché indépendantes des pièces MEME il y a quelque temps, qui ne suivent pas du tout la tendance du marché. Filtrer ces devises et aller longtemps après la percée, cette méthode peut générer des bénéfices dans certaines conditions de marché. Cependant, la méthode la plus courante dans le domaine du trading quantitatif consiste à utiliser la corrélation positive pour le trading par paires. Cet article présente brièvement cette stratégie.
Le trading de paires de crypto-monnaies est une stratégie de trading basée sur l’arbitrage statistique, qui cherche à tirer profit des écarts de prix en achetant et en vendant simultanément deux contrats perpétuels de crypto-monnaies hautement corrélés. Cet article présentera les principes de la stratégie, le mécanisme de profit, la méthode de filtrage des devises, les risques potentiels et les moyens de l’améliorer, et fournira quelques exemples pratiques de code Python.
Les stratégies de trading par paires s’appuient sur la corrélation historique entre les prix de deux crypto-monnaies. Lorsque deux devises sont fortement corrélées, leurs prix évoluent à peu près de manière synchronisée. Si à un moment donné le rapport des prix des deux s’écarte significativement, on peut considérer qu’il s’agit d’une anomalie temporaire et les prix auront tendance à revenir à des niveaux normaux. Le marché des devises numériques est fortement interconnecté. Lorsqu’une devise numérique majeure (comme le Bitcoin) subit des fluctuations importantes, cela déclenche généralement une réaction en chaîne parmi les autres devises numériques. Certaines devises peuvent avoir une corrélation positive très évidente, et cette corrélation peut être maintenue, car elles appartiennent aux mêmes institutions d’investissement, aux mêmes teneurs de marché et au même parcours. Certaines devises sont corrélées négativement, mais il existe moins de devises corrélées négativement et, comme elles sont toutes affectées par la tendance générale du marché, elles présentent souvent des tendances de marché cohérentes.
Supposons que la pièce A et la pièce B aient une forte corrélation de prix. À un moment donné, la valeur moyenne du ratio de prix A/B est de 1. Si à un certain moment, le rapport de prix A/B s’écarte de la hausse 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 ratio de prix A/B est inférieur à 0,999 : ouvrez une position longue sur A et une position courte sur B.
La clé de la rentabilité réside dans les gains de différence de prix lorsque les prix s’écartent et reviennent à la normale. Étant donné que les écarts de prix sont généralement de courte durée, les traders peuvent clôturer leurs positions lorsque les prix reviennent à la moyenne et profiter de la différence.
Ces codes peuvent être utilisés directement, mais il est préférable de télécharger Anancoda et de le déboguer dans un notebook Jupyer. Inclut directement des packages pour l’analyse de données couramment utilisées.
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
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) # 获取所有的正在交易的交易对
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 trading spécifiée auprès de la bourse Binance et de stocker ces données dans un Pandas DataFrame. Les données K-line incluent le prix d’ouverture, le prix le plus élevé, le prix le plus bas, le prix de clôture, le volume des transactions et d’autres informations. Cette fois, nous utilisons principalement les données du cours 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
La quantité de données est relativement importante. Afin de pouvoir télécharger plus rapidement, seules les données horaires de la ligne K des trois derniers mois ont été obtenues. df_close contient les données de cours de clôture pour 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')
Définit un objet Exchange pour le backtest suivant
class Exchange:
def __init__(self, trade_symbols, fee=0.0002, initial_balance=10000):
self.initial_balance = initial_balance #初始的资产
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 #扣除手续费
self.account['USDT']['fee'] += price*amount*self.fee
self.account[symbol]['fee'] += price*amount*self.fee
if cover_amount > 0: #先平仓
self.account['USDT']['realised_profit'] += -direction*(price - self.account[symbol]['hold_price'])*cover_amount #利润
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): #对资产进行更新
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)
Le calcul de corrélation est une méthode statistique utilisée pour mesurer la relation linéaire entre deux variables. La méthode de calcul de corrélation la plus couramment utilisée est le coefficient de corrélation de Pearson. Vous trouverez ci-dessous les principes, les formules et les méthodes de mise en œuvre pour le calcul de corrélation. Le coefficient de corrélation de Pearson est utilisé pour mesurer la relation linéaire entre deux variables, et sa plage de valeurs est comprise entre -1 et 1 :
Le coefficient de corrélation de Pearson détermine la corrélation entre deux variables en calculant leur covariance et leur écart type. La formule est la suivante :
[ \rho_{X,Y} = \frac{\text{cov}(X,Y)}{\sigma_X \sigma_Y} ]
dans:
Bien sûr, vous n’avez pas à vous soucier outre mesure de la manière dont elle est calculée. Vous pouvez calculer la corrélation de toutes les devises en utilisant une seule ligne de code Python. L’image 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. On peut voir que de grandes zones sont rouge foncé, donc la corrélation positive de la monnaie numérique est très forte.

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);
Sur la base de la corrélation, les 20 paires de devises les plus pertinentes sont sélectionnées. Les résultats sont les suivants. Leurs corrélations sont très fortes, toutes supérieures à 0,99.
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()
# 移除自身相关性(即对角线上的值)
corr_pairs = corr_pairs[corr_pairs != 1]
sorted_corr_pairs = corr_pairs.sort_values(kind="quicksort")
# 提取最相关和最不相关的前20个币种对
most_correlated = sorted_corr_pairs.tail(40)[::-2]
print("最相关的前20个币种对:")
print(most_correlated)
Le code de backtest spécifique est le suivant. La stratégie de démonstration se concentre sur l’observation du rapport de prix de deux crypto-monnaies (IOTA et ZIL) et sur le trading en fonction des variations de ce rapport. Les étapes spécifiques sont les suivantes :
initialisation:
e, le solde initial est de 10 000 $ et les frais de transaction sont de 0,02 %.avg。value = 1000。Traiter de manière itérative les données de prix:
df_close。diff。aim_value, pour chaque écart de 0,01, échangez une valeur. Et décidez des opérations d’achat et de vente en fonction des positions courantes du compte et des conditions de prix.pair_a et acheterpair_b fonctionner.pair_a et vendrepair_b fonctionner.Moyenne ajustée:
avg, afin de refléter le dernier rapport de prix.Mise à jour des comptes et des enregistrements:
res_list。Résultat de sortie:
res_list Convertir en dataframeres, pour 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定义放在评论区
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);
Au total, 4 groupes de devises ont été rétro-testés et les résultats ont été relativement idéaux. Les calculs de corrélation actuels utilisent des données futures, ils ne sont donc pas très précis. Cet article divise également les données en deux parties, en fonction de la corrélation calculée à l’avant et des transactions backtestées à l’arrière. Les résultats étaient un peu décevants mais toujours assez bons. Il appartient à l’utilisateur d’exercer la vérification.

Bien que la stratégie de trading par paires puisse être rentable en théorie, elle comporte néanmoins certains risques dans la pratique : la corrélation entre les devises peut changer au fil du temps, entraînant l’échec de la stratégie ; dans des conditions de marché extrêmes, les écarts de prix peuvent augmenter, entraînant des pertes importantes ; la faible liquidité de certaines devises peut rendre les transactions difficiles à exécuter ou augmenter les coûts ; les frais générés par les transactions fréquentes peuvent éroder les bénéfices.
Pour réduire le risque et améliorer la stabilité de la stratégie, 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 trading dans le temps ; définir des points de stop loss et de take profit pour contrôler la perte maximale d’une seule transaction ; échangez plusieurs paires de pièces pour diversifier les risques.
La stratégie de trading de paires de devises numériques permet de réaliser des bénéfices en tirant parti de la corrélation entre les prix des devises et en effectuant des opérations d’arbitrage lorsque les prix s’écartent. Cette stratégie présente une faisabilité théorique élevée. Un code source de stratégie en temps réel simple basé sur cette stratégie sera publié ultérieurement. Si vous avez d’autres questions ou avez besoin d’une discussion plus approfondie, n’hésitez pas à communiquer.