Recientemente, vi el diario cuantitativo de BuOu
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.
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.
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
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
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
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')
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)
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.
El coeficiente de correlación de Pearson determina la correlación entre dos variables calculando su covarianza y desviación estándar.
en el que:
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.
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)
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.
e
con un saldo inicial de $10,000 y una comisión de transacción de 0.02%.avg
.value = 1000
.df_close
.diff
.aim_value
Las 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.pair_a
y comprarpair_b
operations.pair_a
y venderpair_b
las operaciones se realizan.avg
para reflejar los últimos índices de precios.res_list
.res_list
al marco de datosres
para 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.
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.
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.