En la carga de los recursos... Cargando...

Explicación detallada de la estrategia de negociación de pares de divisas digitales

El autor:FMZ~Lydia, Creado: 2024-07-08 11:41:23, Actualizado: 2024-07-12 15:54:35

img

Introducción

Recientemente, vi el diario cuantitativo de BuOu mencionando que puedes usar monedas negativamente correlacionadas para seleccionar monedas y abrir posiciones para obtener ganancias basadas en avances en la diferencia de precios. Las monedas digitales están básicamente correlacionadas positivamente, y solo unas pocas monedas están correlacionadas negativamente, a menudo con condiciones especiales de mercado, como las condiciones de mercado independientes de las monedas MEME, que son completamente diferentes de la tendencia del mercado. Estas monedas se pueden seleccionar y continuar mucho después del avance. Este método puede obtener ganancias en condiciones específicas de mercado. Sin embargo, el método más común en el campo del comercio cuantitativo es usar correlación positiva para el comercio en pares. Este artículo presentará brevemente esta estrategia.

El comercio de pares de divisas digitales es una estrategia comercial basada en el arbitraje estadístico, que compra y vende simultáneamente dos criptomonedas altamente correlacionadas para obtener ganancias de las desviaciones de precios.

Principio de la estrategia

Las estrategias de negociación de pares se basan en la correlación histórica entre los precios de dos monedas digitales. Cuando los precios de dos monedas muestran una fuerte correlación, sus tendencias de precios generalmente están sincronizadas. Si la relación de precios entre las dos se desvía significativamente en un cierto momento, se puede considerar una anormalidad temporal y el precio tenderá a volver a niveles normales. El mercado de divisas digitales está altamente interconectado. Cuando una moneda digital importante (como Bitcoin) fluctúa significativamente, generalmente desencadena una reacción coordinada en otras monedas digitales. Algunas monedas pueden tener una correlación positiva muy obvia que puede durar debido a las mismas instituciones de inversión, los mismos creadores de mercado y la misma pista.

Supongamos que la moneda A y la moneda B tienen una alta correlación de precios. En un momento determinado, el valor promedio de la relación de precios A/B es 1. Si en un momento determinado, la relación de precios A/B se desvía en más de 0,001, es decir, más de 1,001, entonces puedes operar de las siguientes maneras: Abrir una posición larga en B y abrir una posición corta en A. Por el contrario, cuando la relación de precios A/B es inferior a 0,999: Abrir una posición larga en A y abrir una posición corta en B.

La clave de la rentabilidad radica en las ganancias de diferencias cuando los precios se desvían de la media y vuelven a la normalidad.

Prepare los datos

Importar la biblioteca correspondiente

Estos códigos se pueden utilizar directamente. Lo mejor es descargar Anancoda y depurarlo en Jupyer notebook. Incluye paquetes para el análisis de datos de uso común directamente.

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

Obtener todos los pares comerciales que se negocian

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

Descargar la función de línea K

La función GetKlines tiene como función principal obtener los datos históricos de la línea K del contrato perpetuo del par de operaciones especificado de la bolsa Binance y almacenar los datos en un marco de datos Pandas. Los datos de la línea K incluyen información como precio de apertura, precio más alto, precio más bajo, precio de cierre y volumen de operaciones.

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

Descargar datos

El volumen de datos es relativamente grande. Con el fin de descargar más rápido, sólo se obtienen los datos de la línea K por hora de los últimos tres meses. df_close contiene los datos de precios de cierre de todas las monedas.

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 pruebas de retroceso

Definimos un objeto de intercambio para el siguiente 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álisis de correlación con las monedas de filtro

El cálculo de correlación es un método en estadística utilizado para medir la relación lineal entre dos variables. El método de cálculo de correlación más comúnmente utilizado es el coeficiente de correlación de Pearson.

  • 1 indica una correlación positiva perfecta, donde las dos variables siempre cambian en sincronía. Cuando una variable aumenta, la otra también aumenta proporcionalmente. Cuanto más cerca esté de 1, más fuerte será la correlación.
  • -1 indica una correlación negativa perfecta, donde las dos variables siempre cambian en direcciones opuestas.
  • 0 significa que no hay correlación lineal, no hay una relación lineal recta entre las dos variables.

El coeficiente de correlación de Pearson determina la correlación entre dos variables calculando su covarianza y desviación estándar.

img

en el que:

  • imges el coeficiente de correlación de Pearson entre las variables X y Y.
  • imges la covarianza de X y Y.
  • imgyimgson las desviaciones estándar de X y Y, respectivamente.

Por supuesto, usted no necesita preocuparse demasiado acerca de cómo se calcula. Usted puede usar 1 línea de código en Python para calcular la correlación de todas las monedas. La figura muestra un mapa de calor de correlación. El rojo representa correlación positiva, el azul representa correlación negativa, y cuanto más oscuro sea el color, más fuerte es la correlación. Se puede ver que la mayor parte del área es de color rojo oscuro, por lo que la correlación positiva de las monedas digitales es muy fuerte.

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

Basándose en la correlación, se seleccionan los 20 pares de divisas más correlacionados. Los resultados son los siguientes.

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

El código correspondiente es el siguiente:

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)

Verificación de las pruebas de retroceso

El código de backtest específico es el siguiente. La estrategia de demostración observa principalmente la relación de precios de dos criptomonedas (IOTA y ZIL) y opera de acuerdo con los cambios en esta relación.

  1. Inicialización:
  • Define los pares de operaciones (pair_a = IOTA, pair_b = ZIL).
  • Crear un objeto de intercambioecon un saldo inicial de $10,000 y una comisión de transacción de 0.02%.
  • Calcular el índice de precio medio inicialavg.
  • Establecer el valor de la transacción inicialvalue = 1000.
  1. Iteración sobre los datos de precios:
  • Recorrer los datos de precios en cada momentodf_close.
  • Calcula la desviación de la relación de precio actual de la mediadiff.
  • El valor objetivo de la transacción se calcula sobre la base de la desviaciónaim_valueLas operaciones de compra y venta se determinan en función de la posición de la cuenta corriente y de la situación de los precios.
  • Si la desviación es demasiado grande, ejecutar venderpair_ay comprarpair_b operations.
  • Si la desviación es demasiado pequeña, comprarpair_ay venderpair_blas operaciones se realizan.
  1. Ajuste la media:
  • Actualiza la relación de precios mediosavgpara reflejar los últimos índices de precios.
  1. Actualizar las cuentas y registros:
  • Actualizar la información sobre la posición y el saldo de la cuenta de cambio.
  • Registrar el estado de la cuenta en cada etapa (activos totales, activos mantenidos, comisiones de transacción, posiciones largas y cortas) parares_list.
  1. Resultado de salida:
  • Convierteres_listal marco de datosrespara su análisis y presentación.
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 grupos de monedas fueron sometidos a backtest, y los resultados fueron ideales. El cálculo de correlación actual utiliza datos futuros, por lo que no es muy preciso. Este artículo también divide los datos en dos partes, basados en el cálculo anterior de correlación y el posterior comercio de backtest. Los resultados son un poco diferentes pero no malos. Dejamos que el usuario practique y verifique.

img

Potenciales riesgos y formas de mejorar

Aunque la estrategia de negociación de pares puede ser rentable en teoría, todavía hay algunos riesgos en el funcionamiento real: la correlación entre las monedas puede cambiar con el tiempo, causando que la estrategia falle; en condiciones extremas de mercado, las desviaciones de precios pueden aumentar, lo que resulta en mayores pérdidas; la baja liquidez de ciertas monedas puede hacer que las transacciones sean difíciles de ejecutar o aumentar los costos; y las tarifas generadas por las transacciones frecuentes pueden erosionar las ganancias.

Para reducir los riesgos y mejorar la estabilidad de las estrategias, pueden considerarse las siguientes medidas de mejora: recalcular regularmente la correlación entre las monedas y ajustar los pares de negociación de manera oportuna; establecer puntos de stop loss y take profit para controlar la pérdida máxima de una sola transacción; negociar múltiples pares de divisas al mismo tiempo para diversificar los riesgos.

Conclusión

La estrategia de negociación de pares de divisas digitales logra ganancias aprovechando la correlación de los precios de las divisas y realizando operaciones de arbitraje cuando los precios se desvían. Esta estrategia tiene una alta viabilidad teórica. Un código fuente de estrategia de negociación en vivo simple basado en esta estrategia se lanzará más adelante. Si tiene más preguntas o necesita una discusión adicional, no dude en comunicarse.


Más.