Un approccio basato su dati (Data-Driven) al trading bitcoin

9 minute read Published:

Come si comportano i mercati Bitcoin? Quali sono le cause degli improvvisi picchi e cali dei valori di criptovaluta? I mercati di diversi altcoin sono inseparabilmente collegati o in gran parte indipendenti? Come possiamo prevedere cosa accadrà dopo?

Gli articoli sulle criptovalute, come Bitcoin ed Ethereum, sono pieni di speculazioni, con centinaia di esperti autoproclamati che sostengono di conoscere quello che succederà domani. Quello che manca a molte di queste analisi è una solida base di dati e statistiche sui cui basare le proprie affermazioni.

L’obiettivo di questo articolo è di fornire una facile introduzione all’analisi delle criptovalute utilizzando Python. Passeremo attraverso un semplice script Python per recuperare, analizzare e visualizzare i dati su diverse criptovalute. Nel processo, scopriremo una tendenza interessante nel modo in cui questi mercati volatili si comportano e in che modo si stanno evolvendo.

ATTENZIONE: in nessun caso questo articolo, l’autore o l’azienda possono essere coinvolti se qualcuno dovesse perdere soldi o criptovaluta.

Passaggio 1: set-up del nostro laboratorio di analisi dei dati

L’articolo è destinato ad appassionati, ingegneri e scienziati che si occupano di data analysis con più o meno esperienza.

I requisiti sono una conoscenza di base di Python e della riga di comando.

Passaggio 1.1 - Installare Miniconda

Il modo più semplice per installare da zero le dipendenze per questo progetto consiste nell’utilizzare Miniconda, un ecosistema e gestore delle dipendenze Python preconfezionato.

Per configurare Miniconda, ti consiglio di seguire le istruzioni di installazione ufficiali - https://conda.io/miniconda.html.

Se sei un utente esperto e non vuoi usare Miniconda, puoi saltare alla sezione 2 risolvendo da solo le dipendenze del progetto.

Passaggio 1.2 - Impostazione di un ambiente di progetto Anaconda

Una volta installato Miniconda, possiamo creare un nuovo ambiente per mantenere le nostre dipendenze organizzate.

Eseguire:

conda create -n analisi-crypto python=3.6

per creare un nuovo ambiente per il nostro progetto.

Poi attiviamo il nostro ambiente:

source activate analisi-crypto

Attenzione su Windows il comando è diverso.

Ora possiamo installare i pacchetti che sono necesari per il nostro progetto (verficare di essere nell’ambiente conda appena creato):

Infine, lanciare

conda install numpy pandas nb_conda plotly quandl 
conda install jupyter

per installare le dipendenze richieste nell’ambiente. Questo potrebbe richiedere alcuni minuti per scaricare e installare i pacchetti.

Step 1.3 - Jupyter Notebook

Una volta che l’ambiente e le dipendenze sono tutti impostati, è sufficiente avviare un nuovo jupyter notebook:

jupyter notebook

Con questa interfaccia creiamo un nuovo progetto usando l’ambiente conda appena creato:

Jupyter Notebook: creare con il giusto ambiente

Di seguito i contenuti del file notebook (ancora da mettere a posto)

Python trading crypto: analisi dei dati

In questo file analizziamo il mercato delle criptovalute usando python.

Per prima cosa carichiamo le dipendenze di cui abbiamo bisogno:

import os
import numpy as np
import pandas as pd
import pickle
import quandl
from datetime import datetime

Poi importiamo Plotly e abilitiamo la modalità offline:

import plotly.offline as py
import plotly.graph_objs as go
import plotly.figure_factory as ff
py.init_notebook_mode(connected=True)

Come sorgente dei dati useremo QUANDL perché offre le API gratuitamente. Per facilitare questo recupero dei dati definiremo una funzione per scaricare e memorizzare nella cache i set di dati di Quandl, in modo da limitare il nostro accesso online e non sovraccaricare inutilmente il servizio:

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

Stiamo utilizzando pickle per serializzare e salvare i dati scaricati come file, il che impedirà al nostro script di scaricare nuovamente gli stessi dati ogni volta che eseguiamo lo script. La funzione restituirà i dati come un dataframe di Pandas. Se non hai familiarità con i dataframes, puoi considerarli come fogli di calcolo tipo Excel superpotenti.

[Todo] Potremmo voler definire la stessa funzione per CryptoWatch

Vediamo i dati di Kraken

Scarichiamo i dati da Kraken per iniziare:

# Pull Kraken BTC price exchange data
#btc_usd_price_kraken = get_quandl_data('BCHARTS/KRAKENUSD')
btc_eur_price_kraken = get_quandl_data('BCHARTS/KRAKENEUR')
Downloading BCHARTS/KRAKENEUR from Quandl
Cached BCHARTS/KRAKENEUR at BCHARTS-KRAKENEUR.pkl

Una volta scaricati i dati possiamo ispezionare quelli più vecchi con il metodo head

#btc_usd_price_kraken.head()
btc_eur_price_kraken.head()
Open High Low Close Volume (BTC) Volume (Currency) Weighted Price
Date
2014-01-08 624.01000 640.00000 600.00000 613.47231 331.481650 204958.394492 618.309926
2014-01-09 613.47231 626.99990 590.00000 614.22456 583.334331 355744.845866 609.847265
2014-01-10 615.00000 630.00000 605.00000 625.73866 323.047379 199327.057887 617.021127
2014-01-11 626.17401 658.99999 623.30000 653.89888 561.256792 361578.576476 644.230202
2014-01-12 653.89888 659.98000 613.00555 625.26660 477.757931 302729.099050 633.645366

Poi possiamo tracciare un grafico per controllare che corrisponda a quelli che vediamo online:

# Chart the BTC pricing data
#btc_trace = go.Scatter(x=btc_usd_price_kraken.index, y=btc_usd_price_kraken['Weighted Price'])
btc_trace = go.Scatter(x=btc_eur_price_kraken.index, y=btc_eur_price_kraken['Weighted Price'])
py.iplot([btc_trace])

Il grafico usa Plotly per generare la visualizzazione invece del noto Matplotlib: Plotly è un’ottima scelta poiché produce grafici completamente interattivi usando D3.js e sono molto semplici da incorporare nelle pagine web.

Diversi market

L’obiettivo ora è di scaricare i dati da diversi market e vedere se ci sono alcuni che “anticipano” gli altri, in modo da poter sfruttare questa informazione.

# Pull pricing data for 4 more BTC exchanges
exchanges = ['KRAKEN','COINBASE','BITSTAMP']

exchange_data = {}

for exchange in exchanges:
    exchange_code = 'BCHARTS/{}USD'.format(exchange)
    btc_exchange_df = get_quandl_data(exchange_code)
    exchange_data[exchange] = btc_exchange_df
Loaded BCHARTS/KRAKENUSD from cache
Loaded BCHARTS/COINBASEUSD from cache
Loaded BCHARTS/BITSTAMPUSD from cache

Mettiamoli tutti in un unico dataframe:

def merge_dfs_on_column(dataframes, labels, col):
    '''Merge a single column of each dataframe into a new combined dataframe'''
    series_dict = {}
    for index in range(len(dataframes)):
        series_dict[labels[index]] = dataframes[index][col]
        
    return pd.DataFrame(series_dict)

btc_usd_datasets = merge_dfs_on_column(list(exchange_data.values()), list(exchange_data.keys()), 'Weighted Price')
btc_usd_datasets.tail()
BITSTAMP COINBASE KRAKEN
Date
2017-12-01 10287.373047 10293.184141 10296.380966
2017-12-02 10927.843358 10946.056300 10926.747343
2017-12-03 11249.499550 11317.806361 11274.703356
2017-12-04 11314.695288 11389.214464 11387.470641
2017-12-05 11616.819714 11655.572330 11640.176467
def df_scatter(df, title, seperate_y_axis=False, y_axis_label='', scale='linear', initial_hide=False):
    '''Generate a scatter plot of the entire dataframe'''
    label_arr = list(df)
    series_arr = list(map(lambda col: df[col], label_arr))
    
    layout = go.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'
        
    # Form Trace For Each Series
    trace_arr = []
    for index, series in enumerate(series_arr):
        trace = go.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 = go.Figure(data=trace_arr, layout=layout)
    py.iplot(fig)
# Plot all of the BTC exchange prices
df_scatter(btc_usd_datasets, 'Bitcoin Price (USD) By Exchange')
Plot 3
# Remove "0" values
btc_usd_datasets.replace(0, np.nan, inplace=True)
# Plot the revised dataframe
df_scatter(btc_usd_datasets, 'Bitcoin Price (USD) By Exchange')

Plot 7
# Calculate the average BTC price as a new column
btc_usd_datasets['avg_btc_price_usd'] = btc_usd_datasets.mean(axis=1)
# Plot the average BTC price
btc_trace = go.Scatter(x=btc_usd_datasets.index, y=btc_usd_datasets['avg_btc_price_usd'])
py.iplot([btc_trace])
Plot 5

Altre Criptovalute

Prepariamo una funzione per gestire i file di tipo json:

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

Scarichiamo i dati da Poloniex:

base_polo_url = 'https://poloniex.com/public?command=returnChartData&currencyPair={}&start={}&end={}&period={}'
start_date = datetime.strptime('2015-01-01', '%Y-%m-%d') # get data from the start of 2015
end_date = datetime.now() # up until today
pediod = 86400 # pull daily data (86,400 seconds per day)

def get_crypto_data(poloniex_pair):
    '''Retrieve cryptocurrency data from poloniex'''
    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

Scarichiamo i dati:

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
Downloading https://poloniex.com/public?command=returnChartData&currencyPair=BTC_ETH&start=1420066800.0&end=1512499395.428037&period=86400
Cached https://poloniex.com/public?command=returnChartData&currencyPair=BTC_ETH&start=1420066800.0&end=1512499395.428037&period=86400 at BTC_ETH
Downloading https://poloniex.com/public?command=returnChartData&currencyPair=BTC_LTC&start=1420066800.0&end=1512499395.428037&period=86400
Cached https://poloniex.com/public?command=returnChartData&currencyPair=BTC_LTC&start=1420066800.0&end=1512499395.428037&period=86400 at BTC_LTC
Downloading https://poloniex.com/public?command=returnChartData&currencyPair=BTC_XRP&start=1420066800.0&end=1512499395.428037&period=86400
Cached https://poloniex.com/public?command=returnChartData&currencyPair=BTC_XRP&start=1420066800.0&end=1512499395.428037&period=86400 at BTC_XRP
Downloading https://poloniex.com/public?command=returnChartData&currencyPair=BTC_ETC&start=1420066800.0&end=1512499395.428037&period=86400
Cached https://poloniex.com/public?command=returnChartData&currencyPair=BTC_ETC&start=1420066800.0&end=1512499395.428037&period=86400 at BTC_ETC
Downloading https://poloniex.com/public?command=returnChartData&currencyPair=BTC_STR&start=1420066800.0&end=1512499395.428037&period=86400
Cached https://poloniex.com/public?command=returnChartData&currencyPair=BTC_STR&start=1420066800.0&end=1512499395.428037&period=86400 at BTC_STR
Downloading https://poloniex.com/public?command=returnChartData&currencyPair=BTC_DASH&start=1420066800.0&end=1512499395.428037&period=86400
Cached https://poloniex.com/public?command=returnChartData&currencyPair=BTC_DASH&start=1420066800.0&end=1512499395.428037&period=86400 at BTC_DASH
Downloading https://poloniex.com/public?command=returnChartData&currencyPair=BTC_SC&start=1420066800.0&end=1512499395.428037&period=86400
Cached https://poloniex.com/public?command=returnChartData&currencyPair=BTC_SC&start=1420066800.0&end=1512499395.428037&period=86400 at BTC_SC
Downloading https://poloniex.com/public?command=returnChartData&currencyPair=BTC_XMR&start=1420066800.0&end=1512499395.428037&period=86400
Cached https://poloniex.com/public?command=returnChartData&currencyPair=BTC_XMR&start=1420066800.0&end=1512499395.428037&period=86400 at BTC_XMR
Downloading https://poloniex.com/public?command=returnChartData&currencyPair=BTC_XEM&start=1420066800.0&end=1512499395.428037&period=86400
Cached https://poloniex.com/public?command=returnChartData&currencyPair=BTC_XEM&start=1420066800.0&end=1512499395.428037&period=86400 at BTC_XEM

Vediamo ad esempio gli ultimi dati ethereum:

altcoin_data['ETH'].tail()

L’output è:

close high low open quoteVolume volume weightedAverage
date
2017-12-01 0.042447 0.044571 0.042202 0.043615 82865.313025 3589.518042 0.043317
2017-12-02 0.041811 0.042657 0.041600 0.042447 59068.068770 2484.743072 0.042066
2017-12-03 0.041286 0.042200 0.040400 0.041966 90700.958596 3744.967920 0.041289
2017-12-04 0.040090 0.041247 0.039802 0.041120 66678.957627 2702.009705 0.040523
2017-12-05 0.039287 0.040430 0.038500 0.040090 68469.662453 2695.220066 0.039364

Trasformiamo il prezzo da BTC a USD:

# Calculate USD Price as a new column in each altcoin dataframe
for altcoin in altcoin_data.keys():
    altcoin_data[altcoin]['price_usd'] =  altcoin_data[altcoin]['weightedAverage'] * btc_usd_datasets['avg_btc_price_usd']

Mettiamo tutto in un unico dataframe:

# Merge USD price of each altcoin into single dataframe 
combined_df = merge_dfs_on_column(list(altcoin_data.values()), list(altcoin_data.keys()), 'price_usd')

Aggiungiamo anche bitcoin:

# Add BTC price to the dataframe
combined_df['BTC'] = btc_usd_datasets['avg_btc_price_usd']

Facciamo il grafico dell’andamento dei prezzi:

# Chart all of the altocoin prices
df_scatter(combined_df, 'Cryptocurrency Prices (USD)', seperate_y_axis=False, y_axis_label='Coin Value (USD)', scale='log')

Prezzi criptovalute altcoins su scala logaritmica 2015-2017

Ad occhio sembrano correlati, vediamo se lo sono anche calcolando il coefficiente di correlazione di Pearson:

# Calculate the pearson correlation coefficients for cryptocurrencies in 2016
combined_df_2016 = combined_df[combined_df.index.year == 2016]
combined_df_2016.pct_change().corr(method='pearson')
DASH ETC ETH LTC SC STR XEM XMR XRP BTC
DASH 1.000000 0.003332 0.119495 -0.012434 0.024871 0.056525 0.014726 0.122695 0.085853 -0.012524
ETC 0.003332 1.000000 -0.183189 -0.131160 -0.007839 -0.102969 -0.080789 -0.103963 -0.053647 -0.167509
ETH 0.119495 -0.183189 1.000000 -0.067909 0.168096 0.032818 0.042654 0.086897 0.082004 -0.009609
LTC -0.012434 -0.131160 -0.067909 1.000000 0.010524 0.112398 0.161885 0.132392 0.051529 0.751519
SC 0.024871 -0.007839 0.168096 0.010524 1.000000 0.142072 0.105709 0.047593 0.019203 0.033330
STR 0.056525 -0.102969 0.032818 0.112398 0.142072 1.000000 0.225057 0.028635 0.318613 0.078469
XEM 0.014726 -0.080789 0.042654 0.161885 0.105709 0.225057 1.000000 0.017518 0.101278 0.228884
XMR 0.122695 -0.103963 0.086897 0.132392 0.047593 0.028635 0.017518 1.000000 0.028007 0.131622
XRP 0.085853 -0.053647 0.082004 0.051529 0.019203 0.318613 0.101278 0.028007 1.000000 0.042646
BTC -0.012524 -0.167509 -0.009609 0.751519 0.033330 0.078469 0.228884 0.131622 0.042646 1.000000

Vediamo se riusciamo a fare una heatmap che meglio esprima visivamente la correlazione:

def correlation_heatmap(df, title, absolute_bounds=True):
    '''Plot a correlation heatmap for the entire dataframe'''
    heatmap = go.Heatmap(
        z=df.corr(method='pearson').as_matrix(),
        x=df.columns,
        y=df.columns,
        colorbar=dict(title='Coefficiente di Pearson'),
    )
    
    layout = go.Layout(title=title)
    
    if absolute_bounds:
        heatmap['zmax'] = 1.0
        heatmap['zmin'] = -1.0
        
    fig = go.Figure(data=[heatmap], layout=layout)
    py.iplot(fig)

Vediamo il grafico:

correlation_heatmap(combined_df_2016.pct_change(), "Cryptocurrency Correlations in 2016")

Correlazione tra le criptovalute altcoins nel 2016

combined_df_2017 = combined_df[combined_df.index.year == 2017]
combined_df_2017.pct_change().corr(method='pearson')
DASH ETC ETH LTC SC STR XEM XMR XRP BTC
DASH 1.000000 0.352844 0.473588 0.293306 0.219287 0.169373 0.307895 0.458597 0.079461 0.311134
ETC 0.352844 1.000000 0.579287 0.479386 0.287634 0.201241 0.312852 0.420781 0.114170 0.429115
ETH 0.473588 0.579287 1.000000 0.367756 0.359167 0.252408 0.385459 0.541598 0.174040 0.431454
LTC 0.293306 0.479386 0.367756 1.000000 0.342366 0.332011 0.305147 0.428446 0.353607 0.432807
SC 0.219287 0.287634 0.359167 0.342366 1.000000 0.406521 0.344048 0.337242 0.255147 0.357286
STR 0.169373 0.201241 0.252408 0.332011 0.406521 1.000000 0.387098 0.309623 0.536093 0.213268
XEM 0.307895 0.312852 0.385459 0.305147 0.344048 0.387098 1.000000 0.339543 0.289956 0.370930
XMR 0.458597 0.420781 0.541598 0.428446 0.337242 0.309623 0.339543 1.000000 0.245668 0.392209
XRP 0.079461 0.114170 0.174040 0.353607 0.255147 0.536093 0.289956 0.245668 1.000000 0.201873
BTC 0.311134 0.429115 0.431454 0.432807 0.357286 0.213268 0.370930 0.392209 0.201873 1.000000
correlation_heatmap(combined_df_2017.pct_change(), "Cryptocurrency Correlations in 2017")

Correlazione tra le criptovalute altcoins nel 2017

comments powered by Disqus