O recurso está a ser carregado... Carregamento...

Explicação detalhada da estratégia de negociação de pares de moedas digitais

Autora:FMZ~Lydia, Criado: 2024-07-08 11:41:23, Atualizado: 2024-07-12 15:54:35

img

Introdução

Recentemente, vi o Diário Quantitativo do BuOu mencionando que você pode usar moedas negativamente correlacionadas para selecionar moedas e abrir posições para obter lucros com base em avanços de diferença de preço. As moedas digitais são basicamente positivamente correlacionadas e apenas algumas moedas são negativamente correlacionadas, muitas vezes com condições especiais de mercado, como as condições de mercado independentes das moedas MEME, que são completamente diferentes da tendência do mercado. Essas moedas podem ser selecionadas e continuar por muito tempo após o avanço. Este método pode gerar lucros sob condições específicas de mercado. No entanto, o método mais comum no campo da negociação quantitativa é usar correlação positiva para negociação em pares. Este artigo apresentará brevemente essa estratégia.

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.

Princípio da estratégia

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.

Prepare os dados

Importar a biblioteca correspondente

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

Obter todos os pares de negociação sendo negociados

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

Baixe a função K-line

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

Baixar dados

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')

Motor de teste de retorno

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)

Análise de correlação com as moedas de filtro

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.

  • 1 indica uma correlação positiva perfeita, onde as duas variáveis sempre mudam em sincronia. Quando uma variável aumenta, a outra também aumenta proporcionalmente.
  • -1 indica uma correlação negativa perfeita, onde as duas variáveis sempre mudam em direções opostas.
  • 0 significa que não há correlação linear, não há relação linear entre as duas variáveis.

O coeficiente de correlação de Pearson determina a correlação entre duas variáveis, calculando sua covariância e desvio padrão.

img

em que:

  • imgé o coeficiente de correlação de Pearson entre as variáveis X e Y.
  • imgé a covariância de X e Y.
  • imgeimgsão os desvios-padrão de X e Y, respectivamente.

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.

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);

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)

Verificação de retrocesso

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.

  1. Inicialização:
  • Definir pares de negociação (pair_a = IOTA, pair_b = ZIL).
  • Criar um objeto de trocaecom um saldo inicial de 10 mil dólares e uma taxa de transacção de 0,02%.
  • Calcular o rácio de preço médio inicialavg.
  • Definir um valor de transação inicialvalue = 1000.
  1. Iterar sobre os dados de preços:
  • Atravessar os dados de preço em cada ponto de tempodf_close.
  • Calcula o desvio do rácio de preço corrente da médiadiff.
  • O valor-alvo da transacção é calculado com base no desvioaim_valueAs operações de compra e venda são determinadas com base na posição da conta corrente e na situação dos preços.
  • Se o desvio for muito grande, executar vendapair_ae comprarpair_b operations.
  • Se o desvio for muito pequeno, comprarpair_ae venderpair_bOperações são realizadas.
  1. Ajuste a média:
  • Atualiza o rácio de preço médioavgpara refletir os últimos rácios de preços.
  1. Atualizar contas e registos:
  • Atualizar a posição e o saldo da conta de câmbio.
  • Registre o estado da conta em cada etapa (ativos totais, activos detidos, taxas de transacção, posições longas e curtas) parares_list.
  1. Resultado de saída:
  • Converterres_listpara dataframerespara 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.

img

Potenciais riscos e maneiras de melhorar

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.

Conclusão

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.


Mais.