Una aproximación basada en datos sobre los precios de las principales criptomonedas.
El reciente rally y colapso de los mercados de criptomonedas nos han mostrado lo volátiles que pueden ser los mercados. Siempre hablamos del Bitcoin como la moneda que impulsa el mercado y el resto flota con respecto al BTC. ¿Será esta hipótesis cierta? En este post analizamos el mercado de criptomonedas basados en una perspectiva de los datos. Basado en el trabajo de Patrick Triest.
El objetivo es proveer un marco conceptual sobre el mercado del Bitcoin y alt-coins a través de la recolección, análisis y visualización de datos en Python.
Paso 1: Ambiente de Trabajo
El primer paso es tener las librerías necesarias para correr el código. En este caso necesitamos Numpy, Pandas, Pickle, Quandl, DateTime y Plotly. Lo más fácil es usar Anaconda, un ecosistema de python que incluye un IDE (Spyder) y las librerías que necesitamos. Anaconda no incluye por defecto Quantl ni Plotly, por lo que tenemos que instalarlas. Una vez descargado Anaconda, abrimos la consola “Anaconda Prompt” con permisos de administrador y corremos:
conda install -c anaconda quandl conda install -c anaconda plotly
Paso 2: Importar Librerías
Ya con Anaconda instalado (u otro ecosistema) importamos las librerías que son imperativas.
import numpy as np import pandas as pd import pickle import quandl from datetime import datetime import plotly as py
Plotly requiere de tener una cuenta en http://www.plot.ly y generar una api_key. Es un proceso que no requiere más de 2 minutos.
py.tools.set_credentials_file(username='tomymacmillan', api_key='tu propia api key')
Paso 3: Traer la Data
Quandl provee una API gratuita para hacer requests de datos, en nuestro caso, comenzaremos con el precio del Bitcoin. Creamos una función auxiliar que trae la data desde Quandl y la retorna como un DataFrame de Pandas. Pickle lo utilizamos para guardar los datos previniendo que el código vuelva a descargar los datos cada vez que lo corramos.
Pueden buscar más funciones de Quandl para descargar cualquier dato financiero público en: https://www.quandl.com/tools/python
#Quandl def get_quandl_data(quandl_id): '''Download and cache Quandl dataseries''' cache_path = '{}.pkl'.format(quandl_id).replace('/','-') try: f = open(cache_path, 'rb') df = pickle.load(f) print('Loaded {} from cache'.format(quandl_id)) except (OSError, IOError) as e: print('Downloading {} from Quandl'.format(quandl_id)) df = quandl.get(quandl_id, returns="pandas") df.to_pickle(cache_path) print('Cached {} at {}'.format(quandl_id, cache_path)) return df
3.1 Traer Datos desde algún Exchange
Comenzamos con la data de los precios al cierre de Bitcoin en el exchange Kraken.
#Pull Kraken Exchange Pricing Data btc_usd_price_kraken = get_quandl_data('BCHARTS/KRAKENUSD')
Generamos un gráfico para corroborar la integridad de los datos usando Plot.ly.
btc_trace = py.graph_objs.Scatter(x=btc_usd_price_kraken.index, y=btc_usd_price_kraken['Weighted Price']) py.plotly.plot([btc_trace], filename = "btc_trace")
Junto a Plot.ly hacemos la visualización de datos, mucho menos conocido que Matplotlib pero permite hacer integraciones dinámicas a sitios web u otros medios ya que utiliza D3.js para imprimir el gráfico.
Sería una buena práctica comprobar que los gráficos sean similares a los proporcionados por Bloomberg, Google o Yahoo.
3.2 Otro Exchange
De inmediato notamos que existen valores 0 que resultan evidentes al ver el gráfico (y no así con simplemente ver los datos). Necesitamos tomar datos desde otro exchange y completar los espacios faltantes. Otro problema es que las criptomonedas no están reguladas y su precio se rige netamente por la oferta y demanda de cada exchange. Dicho esto es posible arbitrar entre exchange pero eso será otro post. Necesitamos incorporar datasets de otros exchanges y promediar los precios de cierre para obtener un estimador más consistente con el mercado global. Usamos CoinBase, BitStamp e ITBIT (además de Kraken).
# Pull prices for more exchanges exchanges = ['COINBASE','BITSTAMP','ITBIT'] # Dictionary of Dataframes exchange_data = {} exchange_data['KRAKEN'] = btc_usd_price_kraken for exchange in exchanges: exchange_code = 'BCHARTS/{}USD'.format(exchange) btc_exchange_df = get_quandl_data(exchange_code) exchange_data[exchange] = btc_exchange_df
Guardamos cada data en un diccionario de dataframes (pandas).
3.2 Combinar los Datos
Definimos una función merge_dfs_on_column que combina una columna en común de todos los dataframes, en este caso “Weighted Price”.
def merge_dfs_on_column(dataframes, labels, col): series_dict = {} for index in range(len(dataframes)): series_dict[labels[index]] = dataframes[index][col] return pd.DataFrame(series_dict)
Pasamos como argumento la columna llamada “Weighted Price”.
btc_usd_datasets = merge_dfs_on_column(list(exchange_data.values()), list(exchange_data.keys()), 'Weighted Price')
Paso 4: Visualización de Data
Lo que buscamos ahora es generar un gráfico que permita comparar los datos extraídos de cada exchange. Para eso escribimos una función auxiliar que imprime en Plot.ly un gráfico común.
def df_scatter(df, title, seperate_y_axis=False, y_axis_label='', scale='linear', initial_hide=False): '''Plot de todo el dataframe''' label_arr = list(df) series_arr = list(map(lambda col: df[col], label_arr)) layout = py.graph_objs.Layout( title=title, legend=dict(orientation="h"), xaxis=dict(type='date'), yaxis=dict( title=y_axis_label, showticklabels= not seperate_y_axis, type=scale ) ) y_axis_config = dict( overlaying='y', showticklabels=False, type=scale ) visibility = 'visible' if initial_hide: visibility = 'legendonly' trace_arr = [] for index, series in enumerate(series_arr): trace = py.graph_objs.Scatter( x=series.index, y=series, name=label_arr[index], visible=visibility ) # Add seperate axis for the series if seperate_y_axis: trace['yaxis'] = 'y{}'.format(index + 1) layout['yaxis{}'.format(index + 1)] = y_axis_config trace_arr.append(trace) fig = py.graph_objs.Figure(data=trace_arr, layout=layout) py.plotly.plot(fig)
Generamos un gráfico del precio BTC.
df_scatter(btc_usd_datasets, 'Bitcoin Price (USD) By Exchange')
Resulta evidente notar que aún existen valores “0” que afectan el gráfico. Los reemplazamos por NaN.
btc_usd_datasets.replace(0, np.nan, inplace=True)
Generamos el nuevo gráfico arreglado:
df_scatter(btc_usd_datasets, 'Bitcoin Price (USD) By Exchange')
Ahora tenemos un gráfico consistente que nos permite analizar de forma correcta los precios. Nos falta calcular el promedio de cada exchange:
# Calculamos promedios btc_usd_datasets['avg_btc_price_usd'] = btc_usd_datasets.mean(axis=1) # Ploteamos btc_trace = py.graph_objs.Scatter(x=btc_usd_datasets.index, y=btc_usd_datasets['avg_btc_price_usd']) py.plotly.plot([btc_trace])
Paso 5: Alt Coins
Con el precio del Bitcoin listo, necesitamos traer los datos de otras monedas. Usamos la API de Poloniex y usamos dos funciones para descargar y dejar en caché los datos provenientes en JSON.
#Alt coins def get_json_data(json_url, cache_path): '''Download and cache JSON data, return as a dataframe.''' try: f = open(cache_path, 'rb') df = pickle.load(f) print('Loaded {} from cache'.format(json_url)) except (OSError, IOError) as e: print('Downloading {}'.format(json_url)) df = pd.read_json(json_url) df.to_pickle(cache_path) print('Cached {} at {}'.format(json_url, cache_path)) return df
Definimos la función que genera los requests a la API de Poloniex y llama a get_json_data para guardar los datos.
base_polo_url = 'https://poloniex.com/public?command=returnChartData¤cyPair={}&start={}&end={}&period={}' start_date = datetime.strptime('2015-01-01', '%Y-%m-%d') # desde 2015 end_date = datetime.now() # up until today pediod = 86400 # datos diarios, segundos por día def get_crypto_data(poloniex_pair): '''Descarga Data de Polo''' json_url = base_polo_url.format(poloniex_pair, start_date.timestamp(), end_date.timestamp(), pediod) data_df = get_json_data(json_url, poloniex_pair) data_df = data_df.set_index('date') return data_df
Paso 5.1 COIN/USD
Lo que buscamos es tener el precio en dólares de cada moneda, muchas solo publican su precio en relación el BTC y no obtenemos el precio en dólares directamente. Para esto descargamos los datos del tipo de cambio ALT_COIN a BTC y como tenemos el valor del BTC para cada día, obtenemos los valores en USD. Trabajamos con Ethereum, Ethereum Classic, Litecoin, Ripple, Stellar, Dash, Siacoin, Monero y Nem.
Tenemos un diccionario de 9 DataFrames, cada uno con el promedio diario del tipo de cambio.
altcoins = ['ETH','LTC','XRP','ETC','STR','DASH','SC','XMR','XEM'] altcoin_data = {} for altcoin in altcoins: coinpair = 'BTC_{}'.format(altcoin) crypto_price_df = get_crypto_data(coinpair) altcoin_data[altcoin] = crypto_price_df
Tomamos nuestro precio del BTC histórico y lo usamos para obtener el valor en dólares de cada alt coin.
#el precio en dólares será una nueva columna en cada dataframe. for altcoin in altcoin_data.keys(): altcoin_data[altcoin]['price_usd'] = altcoin_data[altcoin]['weightedAverage'] * btc_usd_datasets['avg_btc_price_usd']
Luego, combinamos las columnas de los dataframes obteniendo un solo dataframe con el valor en dólares de cada una de las monedas.
#Usamos la función merge_dfs_on_column que ya tenemos combined_df = merge_dfs_on_column(list(altcoin_data.values()), list(altcoin_data.keys()), 'price_usd')
Finalmente agregamos el precio BTC a nuestro dataframe final.
combined_df['BTC'] = btc_usd_datasets['avg_btc_price_usd']
Usando df_scatter que ya hemos creado, ploteamos todos los precios en escala logarítmica.
Paso 6: Correlación
Al menos en escala logarítmica, los precios parecen estar correlacionados pero no podemos afirmarlo con certeza sin realizar una estudio de las correlaciones. Buscamos armar la matriz de correlación \(\Omega\).
Utilizamos la función de pandas corr() que obtiene el coeficiente de correlación de Pearson para cada columna del dataframe:
$$\rho_i = \frac{Cov_(x,y)}{\sigma_x \sigma_y}$$
Es muy importante notar que series de tiempo sobre precios son probablemente no estacionarias y debemos tomar sus primeras diferencias. En nuestro caso buscamos las correlaciones entre retornos y lo hacemos aplicando pct_change() al data frame.
Probamos con los datos del 2016:
#Rho 2016 combined_df_2016 = combined_df[combined_df.index.year == 2016] combined_df_2016.pct_change().corr(method='pearson')
Y ploteamos lo que Plot.ly conoce como HeatMap.
def correlation_heatmap(df, title, absolute_bounds=True): ''Ploteamos Heatmap''' heatmap = py.graph_objs.Heatmap( z=df.corr(method='pearson').as_matrix(), x=df.columns, y=df.columns, colorbar=dict(title='Pearson Coefficient'), ) layout = py.graph_objs.Layout(title=title) if absolute_bounds: heatmap['zmax'] = 1.0 heatmap['zmin'] = -1.0 fig = py.graph_objs.Figure(data=[heatmap], layout=layout) py.plotly.plot(fig)
correlation_heatmap(combined_df_2016.pct_change(), "Correlaciones 2016")
Recordemos que $$\rho$$ va desde -1 a 1, desde perfectamente inversamente correlacionados a perfectamente directamente correlacionados. En el caso de -1, si A aumenta 10, B disminuye 10.
En la diagonal encontramos $$\rho_i = 1 $$ pues de manera evidente es la variable contra si misma. $$\Omega$$ es simétrica de diagonal 1 y los valores rojos indican correlación positiva y los azules correlación negativa.
Para el 2017, realizamos el mismo ejercicio:
#2017 combined_df_2017 = combined_df[combined_df.index.year == 2017] combined_df_2017.pct_change().corr(method='pearson') correlation_heatmap(combined_df_2017.pct_change(), "Correlaciones 2017")
Conclusiones
¿Qué pasó entre 2016 y 2017? En primer lugar, vemos como las criptomonedas se han vuelvo extremadamente correlacionadas (positivamente) al 2017. Esto dificulta la posibilidad de poder entrar al mercado de las criptomonedas para hacer cobertura o poder armar portafolios balanceados. Al 2016, cubrir el riesgo era posible tomando posiciones con correlaciones negativas (ETH – ETC), y el problema de optimización de Markovitz se resuelve con un portafolio con pequeño riesgo y alto retorno, formando una curva eficiente atractiva para el inversionista. Al 2017, ya no es posible realizar esta misma visión del mercado, al parecer el rally del Bitcoin ha significado que todas las alt coins la sigan (explicado por la correlación) o bien, la fuerza del bitcoin ha llamado la atención a traders especulativos que han llevado al mercado de criptomonedas a moverse en conjunto de forma muy similar. Si bien ahora todo el mercado se mueve en la misma dirección, la correlación de este con otros mercados (ej. acciones) es bastante baja. En efecto, explicar el precio del BTC a través de variables como el dólar, yuan, oro, entre otros, es muy difícil (Forecasting Bitcoin Returns – Arré, Gonzalez, Mac Millan, Prat 2017). De esta manera, traders que busquen carteras que incluyan criptomonedas, aún pueden hacer cobertura con activos de otros mercados, entrar a especular o arbitrar precios de exchanges.
Como nota, lo interesante de las criptomonedas y las API de los Exchange es que permiten poner a prueba algoritmos automatizados (algorithmic trading) de manera bastante sencilla tal de captar pequeñas variaciones en los precios. Esto se presenta como una gran oportunidad ya que en Chile, no tenemos activos tan líquidos que permitan hacer algorithmic trading en su esplendor, en alta frecuencia (HFT).
El próximo post se enfocará en calcular el VaR (Value-At-Risk) y Expected ShortFall de las cryptomonedas para una adecuada gestión del riesgo.