Recentemente, vi o Diário Quantitativo do BuOu
A negociação de pares de moedas digitais é uma estratégia de negociação baseada em arbitragem estatística, que compra e vende simultaneamente duas criptomoedas altamente correlacionadas para obter lucros de desvios de preço.
As estratégias de negociação de pares dependem da correlação histórica entre os preços de duas moedas digitais. Quando os preços de duas moedas mostram uma forte correlação, suas tendências de preço geralmente estão sincronizadas. Se a relação de preço entre as duas desviar significativamente em um determinado momento, pode ser considerada uma anormalidade temporária e o preço tenderá a retornar aos níveis normais. O mercado de moedas digitais é altamente interconectado. Quando uma grande moeda digital (como o Bitcoin) flutua significativamente, geralmente desencadeia uma reação coordenada em outras moedas digitais. Algumas moedas podem ter uma correlação positiva muito óbvia que pode durar devido às mesmas instituições de investimento, os mesmos criadores de mercado e a mesma pista. Algumas moedas estão negativamente correlacionadas, mas há menos moedas negativamente correlacionadas, e como todas elas são afetadas pela tendência do mercado, muitas vezes terão tendências de mercado consistentes.
Suponha que a moeda A e a moeda B tenham uma alta correlação de preço. Em um determinado momento, o valor médio da relação de preço A/B é 1. Se em um determinado momento, a relação de preço A/B se desviar em mais de 0,001, ou seja, mais de 1,001, então você pode negociar das seguintes maneiras: Abra uma posição longa em B e abra uma posição curta em A. Pelo contrário, quando a relação de preço A/B é menor que 0,999: Abra uma posição longa em A e abra uma posição curta em B.
A chave para a rentabilidade reside nos ganhos de spread quando os preços se desviam da média e voltam ao normal.
Esses códigos podem ser usados diretamente. É melhor baixar o Anancoda e depurar no notebook Jupyer. Ele inclui pacotes para análise de dados comumente usados diretamente.
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) # Get all trading pairs being traded
A função principal da função GetKlines é obter os dados históricos da linha K do contrato perpétuo do par de negociação especificado da exchange Binance e armazenar os dados em um Pandas DataFrame. Os dados da linha K incluem informações como preço de abertura, preço mais alto, preço mais baixo, preço de fechamento e volume de negociação.
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
O volume de dados é relativamente grande. A fim de baixar mais rapidamente, apenas os dados de linha K por hora dos últimos três meses são obtidos. df_close contém os dados de preços de fechamento de 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')
Definimos um objeto de troca para o seguinte backtest.
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)
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. O seguinte é o princípio, fórmula e método de implementação do cálculo de correlação.
O coeficiente de correlação de Pearson determina a correlação entre duas variáveis, calculando sua covariância e desvio padrão.
em que:
Claro, você não precisa se preocupar muito com como é calculado. Você pode usar 1 linha de código no Python para calcular a correlação de todas as moedas. 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. Você pode ver que a maior parte da área é vermelho escuro, então a correlação positiva das moedas digitais é 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 correlacionados 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()
# 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)
O código de backtest específico é o seguinte. A estratégia de demonstração observa principalmente a relação de preço de duas criptomoedas (IOTA e ZIL) e negocia de acordo com as mudanças nessa relação.
e
com um saldo inicial de 10 mil dólares e uma taxa de transacção de 0,02%.avg
.value = 1000
.df_close
.diff
.aim_value
As operações de compra e venda são determinadas com base na posição da conta corrente e na situação dos preços.pair_a
e comprarpair_b
operations.pair_a
e venderpair_b
Operações são realizadas.avg
para refletir os últimos rácios de preços.res_list
.res_list
para dataframeres
para análise e apresentação.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);
Um total de 4 grupos de moedas foram backtestados, e os resultados foram ideais. O cálculo de correlação atual usa dados futuros, por isso não é muito preciso. Este artigo também divide os dados em duas partes, com base no cálculo anterior de correlação e na subsequente negociação de backtest. Os resultados são um pouco diferentes, mas não são ruins. Deixamos para o usuário praticar e verificar.
Embora a estratégia de negociação de pares possa ser lucrativa em teoria, ainda há alguns riscos na operação real: a correlação entre moedas pode mudar ao longo do tempo, fazendo com que a estratégia falhe; sob condições extremas de mercado, os desvios de preço podem aumentar, resultando em perdas maiores; a baixa liquidez de certas moedas pode tornar as transações difíceis de executar ou aumentar os custos; e as taxas geradas por transações frequentes podem corroer os lucros.
Para reduzir os riscos e melhorar a estabilidade das estratégias, podem ser consideradas as seguintes medidas de melhoria: recalcular regularmente a correlação entre as moedas e ajustar os pares de negociação em tempo útil; definir pontos de stop loss e take profit para controlar a perda máxima de uma única transação; negociar vários pares de moedas ao mesmo tempo para diversificar os riscos.
A estratégia de negociação de pares de moedas digitais obtém lucro aproveitando a correlação dos preços das moedas e realizando operações de arbitragem quando os preços desviam. Esta estratégia tem alta viabilidade teórica. Um código fonte de estratégia de negociação ao vivo simples baseado nesta estratégia será lançado mais tarde. Se você tiver mais perguntas ou precisar de discussão adicional, sinta-se à vontade para se comunicar.