
Recentemente, vi o diário quantitativo de Buu, que mencionava que você pode usar moedas negativamente correlacionadas para selecionar moedas e abrir posições para obter lucros com base em avanços na diferença de preços. As moedas digitais são basicamente correlacionadas positivamente, e apenas algumas moedas são negativamente correlacionadas. Elas geralmente têm condições especiais de mercado, como as condições de mercado independentes da moeda MEME há algum tempo, que não seguiram a tendência do mercado de forma alguma. Filtrar essas moedas e ir muito tempo após o avanço, esse método pode gerar lucros sob certas condições de mercado. No entanto, o método mais comum no campo de trading quantitativo é usar correlação positiva para pair trading. Este artigo apresentará brevemente essa estratégia.
A negociação de pares de criptomoedas é uma estratégia de negociação baseada em arbitragem estatística, que busca obter lucros com desvios de preços comprando e vendendo simultaneamente dois contratos perpétuos de criptomoedas altamente correlacionados. Este artigo apresentará os princípios da estratégia, o mecanismo de lucro, o método de triagem de moedas, os riscos potenciais e as maneiras de melhorá-lo, além de fornecer alguns exemplos práticos de código Python.
As estratégias de negociação de pares dependem da correlação histórica entre os preços de duas criptomoedas. Quando duas moedas estão fortemente correlacionadas, seus preços se movem em sincronia aproximada. Se em determinado momento a relação de preços dos dois se desviar significativamente, pode-se considerar que se trata de uma anomalia temporária e os preços tenderão a retornar aos níveis normais. O mercado de moeda digital é altamente interconectado. Quando uma grande moeda digital (como Bitcoin) passa por flutuações significativas, isso geralmente desencadeia uma reação em cadeia entre outras moedas digitais. Algumas moedas podem ter uma correlação positiva muito óbvia, e a correlação pode ser sustentada, porque pertencem às mesmas instituições de investimento, aos mesmos formadores de mercado e ao mesmo caminho. Algumas moedas são negativamente correlacionadas, mas há menos moedas negativamente correlacionadas e, como todas são afetadas pela tendência geral do mercado, elas geralmente mostram tendências de mercado consistentes.
Suponha que a moeda A e a moeda B tenham uma alta correlação de preços. Em um determinado momento, o valor médio da relação preço A/B é 1. Se em um determinado momento, a relação preço A/B se desviar do aumento em mais de 0,001, ou seja, mais de 1,001, então você pode negociar das seguintes maneiras: abrir uma posição longa em B e abrir uma posição curta em A . Por outro lado, quando a relação preço A/B for menor que 0,999: abra uma posição longa em A e uma posição curta em B.
A chave para a lucratividade está nos ganhos com a diferença de preço quando os preços se desviam e retornam ao normal. Como os desvios de preço geralmente duram pouco, os traders podem fechar suas posições quando os preços retornam à média e lucrar com a diferença.
Esses códigos podem ser usados diretamente, mas é melhor baixar o Anancoda e depurá-lo em um notebook Jupyer. Inclui diretamente pacotes para análise de dados comumente usados.
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) # 获取所有的正在交易的交易对
A principal função da função GetKlines é obter os dados históricos da linha K do contrato perpétuo do par de negociação especificado da bolsa Binance e armazenar esses dados em um Pandas DataFrame. Os dados da K-line incluem preço de abertura, preço mais alto, preço mais baixo, preço de fechamento, volume de negociação e outras informações. Desta vez, usamos principalmente dados de preços de fechamento.
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
A quantidade de dados é relativamente grande. Para baixar mais rápido, apenas os dados de K-line por hora dos últimos três meses foram obtidos. df_close contém dados de preços de fechamento para todas as moedas
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')
Define um objeto Exchange para o seguinte backtest
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)
O cálculo de correlação é um método em estatística usado para medir a relação linear entre duas variáveis. O método de cálculo de correlação mais comumente usado é o coeficiente de correlação de Pearson. Abaixo estão os princípios, fórmulas e métodos de implementação para cálculo de correlação. O coeficiente de correlação de Pearson é usado para medir a relação linear entre duas variáveis, e seu intervalo de valores está entre -1 e 1:
O coeficiente de correlação de Pearson determina a correlação entre duas variáveis calculando sua covariância e desvio padrão. A fórmula é a seguinte:
[ \rho_{X,Y} = \frac{\text{cov}(X,Y)}{\sigma_X \sigma_Y} ]
em:
Claro, você não precisa se preocupar muito sobre como ele é calculado. Você pode calcular a correlação de todas as moedas usando apenas uma linha de código Python. A figura mostra um mapa de calor de correlação. Vermelho representa correlação positiva, azul representa correlação negativa e quanto mais escura a cor, mais forte a correlação. Pode-se observar que grandes áreas são vermelho-escuras, então a correlação positiva da moeda digital é muito 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);
Com base na correlação, os 20 pares de moedas mais relevantes são selecionados. Os resultados são os seguintes. Suas correlações são muito fortes, todas acima de 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
O código correspondente é o seguinte:
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)
O código de backtest específico é o seguinte. A estratégia de demonstração se concentra em observar a relação de preço de duas criptomoedas (IOTA e ZIL) e negociar com base nas alterações nessa relação. As etapas específicas são as seguintes:
inicialização:
e, o saldo inicial é de US$ 10.000 e a taxa de transação é de 0,02%.avg。value = 1000。Processar iterativamente os dados de preços:
df_close。diff。aim_value, para cada desvio de 0,01, negocie um valor. E decidir sobre operações de compra e venda com base nas posições atuais da conta e nas condições de preço.pair_a e comprarpair_b operar.pair_a e venderpair_b operar.Média ajustada:
avg, para refletir a relação de preços mais recente.Atualizando contas e registros:
res_list。Saída de resultado:
res_list Converter para dataframeres, para posterior análise e apresentação.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);
Um total de 4 grupos de moedas foram testados e os resultados foram relativamente ideais. Os cálculos de correlação atuais usam dados futuros, portanto não são muito precisos. Este artigo também divide os dados em duas partes, com base na correlação calculada na frente e nas transações testadas no passado. Os resultados ficaram um pouco aquém, mas ainda assim foram muito bons. Cabe ao usuário exercer a verificação.

Embora a estratégia de negociação de pares possa ser lucrativa em teoria, ainda existem alguns riscos na operação real: a correlação entre moedas pode mudar ao longo do tempo, fazendo com que a estratégia falhe; em condições extremas de mercado, os desvios de preço podem aumentar, resultando em grandes perdas; a baixa liquidez de certas moedas pode dificultar a execução de transações ou aumentar os custos; as taxas geradas por transações frequentes podem corroer os lucros.
Para reduzir o risco e melhorar a estabilidade da estratégia, as seguintes medidas de melhoria podem ser consideradas: recalcular regularmente a correlação entre moedas e ajustar os pares de negociação a tempo; definir pontos de stop loss e take profit para controlar a perda máxima de uma única transação; negocie vários pares de moedas para diversificar o risco.
A estratégia de negociação de pares de moedas digitais obtém lucros aproveitando a correlação entre os preços das moedas e realizando operações de arbitragem quando os preços se desviam. Essa estratégia tem alta viabilidade teórica. Um código-fonte simples de estratégia em tempo real baseado nessa estratégia será lançado posteriormente. Caso você tenha mais dúvidas ou precise de mais discussão, sinta-se à vontade para entrar em contato.