K-Means Clustering dei dati giornalieri con barre OHLC

K-Means Clustering dei dati giornalieri con barre OHLC

Sommario

SCRIVIMI SU TELEGRAM

Per informazioni, suggerimenti, collaborazioni...

Se è la prima volta che atterri su DataTrading, BENVENUTO!

Lascia che mi presenti. Sono Gianluca, ingegnere, trekker e appassionato di coding, finanza e trading. Leggi la mia storia.

Ho creato DataTrading per aiutare le altre persone ad utilizzare nuovi approcci e nuovi strumenti, ed applicarli correttamente al mondo del trading.

DataTrading vuole essere un punto di ritrovo per scambiare esperienze, opinioni ed idee.

TUTORIAL

In questo articolo descriviamo il concetto di clustering non supervisionato . Nella finanza quantitativa, è estremamente utile trovare gruppi di asset simili o regimi nelle serie di prezzi  degli asset. Può aiutare nello sviluppo di filtri o regole di ingresso e uscita. Questo aiuta a migliorare la redditività per determinate strategie di trading.

Il clustering è un metodo di apprendimento non supervisionato che tenta di suddividere i dati osservativi in ​​sottogruppi o cluster separati. Il risultato che si vuole ottenere con il raggruppamento consiste nel garantire che le osservazioni all’interno dei cluster siano simili tra loro ma diverse dalle osservazioni in altri cluster.

Il clustering è una vasta area di ricerca accademica ed è difficile fornire una completa tassonomia degli algoritmi di clustering in  un solo articolo. Quindi ci concentriamo su una tecnica ampiamente utilizzata nota come K-Means Clustering.

Applichiamo il K-Means Clustering ai dati giornalieri  rappresentati con le “barre OHLC” – open, high, low, close (cioè apertura, massimo, minino, chiusura) .- al fine di identificare cluster separati “di candele”. Questi cluster possono essere utilizzati per verificare se esistono determinati regimi di mercato, come abbiamo visto con i Hidden Markov Models.

K-Means Clustering

K-Means Clustering è una particolare tecnica per identificare sottogruppi o cluster all’interno di una serie di osservazioni. È una tecnica di hard clustering, cioè ogni osservazione è costretta ad avere un’associazione univoca ad un solo cluster. Questo è in contrasto con la tecnica di  soft clustering, o probabilistica, che assegna si limita ad assegnare probabilità di appartenenza al cluster.

Per utilizzare il K-Means Clustering è necessario specificare un parametro K, cioè il numero di cluster desiderati in cui partizionare i dati. Questi K cluster non si sovrappongono e hanno confini “ben definiti” tra di  essi (vedere la figura seguente). Il compito è assegnare ciascuna delle N osservazioni in uno dei K cluster, in cui ogni osservazione appartiene al cluster con la media del vettore feature/osservazione più vicina.

trading-machine-learning-clustering-k-means-boundaries

Matematicamente possiamo definire gli insiemi \(S_k\), con \(k \in \{ 1,\ldots,K \}\), ognuno dei quali contiene gli indici del sottoinsieme delle N osservazioni che si trovano in ogni cluster. Questi insiemi coprono in modo esaustivo tutti gli indici, ovvero ogni osservazione appartiene ad almeno uno degli insiemi \(S_k\) e i cluster si escludono a vicenda con confini “hard”:

  • \({\bf S} = \bigcup_{k=1}^K S_k = \{ 1,\ldots,N \}\)
  • \(S_k \cap S_{k’} = \phi, \quad \forall k \neq k’\)

L’obiettivo di K-Means Clustering è ridurre al minimo la Within-Cluster Variation (WCV), nota anche come Within-Cluster Sum of Squares (WCSS). Questo concetto rappresenta la somma tra i cluster della somma delle distanze da ciascun punto del cluster alla sua media. In altre parole, misura quanto le osservazioni all’interno di un cluster differiscono l’una dall’altra. E’ quindi necessario minimizzare:

\(\begin{eqnarray}\text{argmin}_{\bf S} \sum_{k=1}^K \sum_{{\bf x}_i \in S_k} \| {\bf x}_i – {\bf \mu}_k \|\end{eqnarray}\)

Dove \({\bf \mu}_k\) rappresenta il vettore delle feature medie del cluster k e \({\bf x}_i\) è l’i-esimo vettore delle feature nel cluster k.

Sfortunatamente questa particolare minimizzazione è difficile da risolvere a livello globale, cioè trovare un minimo globale per questo problema è NP-difficile a livello di complessità.

Fortunatamente esistono utili algoritmi euristici per trovare minimi locali accettabili.

L’algoritmo

L’algoritmo euristico per risolvere il K-Means clustering è noto come algoritmo K-Means. È relativamente semplice da descrivere e si compone di due passaggi, il secondo dei quali viene ripetuto fino al completamento:

  1. Assegnare ogni osservazione \({\bf x}_i\) a un cluster casuale k.
  2. Iterare quanto segue finché l’assegnazione del cluster non rimane fissa:
    • Calcolare il vettore delle feature medie di ciascun cluster, il centroide \({\bf \mu}_k\).
    • Assegnare ogni osservazione \({\bf x}_i\) al \({\bf \mu}_k\) più vicino, dove la “vicinanza” è data dalla distanza euclidea standard.

Non descriviamo il motivo per cui questo algoritmo garantisce di trovare un  minimo locale. Per una discussione più dettagliata della teoria matematica alla base dell’algoritmo si può far riferimento ai lavori di James et al (2009) [1] e Hastie et al (2009) [2] .

Poiché le osservazioni sono  inizialmente assegnate ai cluster in modo casuale possiamo notare che la determinazione del miglior minimo locale è fortemente dipendente da queste assegnazioni iniziali. In pratica l’algoritmo è eseguito più volte (l’impostazione predefinita per Scikit-Learn è dieci volte) e viene scelto il miglior minimo locale, ovvero quello che minimizza maggiormente il WCSS.

Criticità

L’algoritmo K-Means non è esente da difetti. Nella finanza quantitativa abbiamo un basso rapporto segnale/rumore nei dati dei prezzi finanziari, quindi l’algoritmo K-Means ha difficoltà ad estrarre il segnale predittivo da usare per le strategie di trading.

La natura dell’algoritmo K-Means prevede la generazione di k cluster, anche se i dati sono molto rumorosi. Questo implica che tali “cluster” non sono distribuzioni di dati veramente separate, ma sono in realtà artefatti di un set di dati rumoroso. E’ uno dei problemi più difficili da affrontare nel trading quantitativo.

Un altro aspetto nello specificare un k consiste che determinati punti dati anomali verranno automaticamente assegnati a un cluster, indipendentemente dal fatto che facciano veramente parte della “distribuzione” che li ha generati o meno. Questo è causato dalla necessità di imporre un confine rigido del cluster. In finanza i punti dati “anomali” non sono rari, non da ultimo a causa di errori o tick negativi, ma anche a causa di flash crash e altri rapidi cambiamenti del prezzo di un asset.

Inoltre, lo stesso metodo di clustering è abbastanza sensibile alle variazioni nel set di dati sottostante. In altre parole, se una serie di prezzi di un asset finanziario è divisa in due gruppi e due diversi algoritmi K-Means sono stati adattati a ciascuno gruppo, condividendo lo stesso parametro k, è molto probabile ottenere due assegnazioni di cluster molto diverse. Questo solleva la domanda sulla robustezza di tale meccanismo su piccoli set di dati finanziari. Come sempre, anche in questo caso può essere utili usare più dati.

Queste criticità motivano  l’introduzione algoritmi di clustering più sofisticati, che esulano dallo scopo di questo articolo sia a causa delle grande gamma dei metodi disponibile che a causa della loro maggiore  complessità matematica. Tuttavia, per coloro che sono interessati ad approfondire il clustering non supervisionato, dovrebbero essere presi in considerazione i seguenti metodi:

Descriviamo ora la simulazione dei dati e all’adattamento dell’algoritmo K-Means ad essi.

Dati simulati

In questo paragrafo descriviamo come applicare il K-Means Clustering ad un insieme di dati simulati, in modo da avere familiarità con la specifica implementazione Scikit-Learn per questo algoritmo. Inoltre descriviamo come la scelta di k nell’algoritmo è estremamente importante per assicurarsi di ottenere buoni risultati.

In particolare descriviamo come campionare tre  separate distribuzioni gaussiane bidimensionali per creare una selezione dei dati osservati. Usiamo quindi l’algoritmo K-Means, con diversi valori del parametro k per dedurre l’appartenenza al cluster. Infine tracciamo il grafico di un confronto tra due diversi valori di k due scelte separate, insieme all’appartenenza al cluster dedotta in base al colore.

Analoghi esercizi con i dati simulati sono stati effettuati  in precedenti articoli. Aiutano a identificare i potenziali difetti nei modelli su dati sintetici, le cui proprietà statistiche sono facilmente controllabili. In questo modo abbiamo una visione approfondita dei limiti di tali modelli prima della loro applicazione su dati finanziari reali, dove sicuramente non conosciamo a priori le proprietà statistiche!

Il primo passo è importare le librerie necessarie. La libreria itertools di Python è utilizzata per concatenare insieme liste di liste durante la generazione di dati campione casuali. Itertools è una libreria estremamente utile, che può far risparmiare una notevole quantità di tempo di sviluppo. Leggere la documentazione è un buon esercizio per qualsiasi sviluppatore o trader in erba.

Le restanti librerie da importare sono NumPy, Matplotlib e la classe KMeans di Scikit-Learn, che risiede nel modulo cluster:

				
					# simulated_data.py

import itertools

import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
				
			

All’interno della funzione __main__ impostiamo prima di tutto il seme casuale in modo che il seguente codice sia completamente riproducibile. Successivamente impostiamo il numero di campioni per ciascun cluster (samples=100), nonché le matrici bidimensionali della media e covarianza di ciascun cluster gaussiano (uno per ogni elemento in ciascuna lista).

La lista norm_dist contiene tre separate liste bidimensionali di osservazioni, una per ciascun cluster. Infine i dati osservati X sono generati concatenando ciascuna di queste sottoliste, utilizzando la libreria itertools:

				
					np.random.seed(1)

# Imposto il numero di campioni, la media e la 
# varianza per ognuno dei tre cluster simulati
samples = 100
mu = [(7, 5), (8, 12), (1, 10)]
cov = [
    [[0.5, 0], [0, 1.0]],
    [[2.0, 0], [0, 3.5]],
    [[3, 0], [0, 5]],
]

# Generazione di una lista di cluster 2D
norm_dists = [
    np.random.multivariate_normal(m, c, samples) 
    for m, c in zip(mu, cov)
]
X = np.array(list(itertools.chain(*norm_dists)))
				
			

Come con molte implementazioni di modelli di machine learning con Scikit-Learn, l’API è estremamente semplice. In questo caso consiste nell’inizializzare la classe KMeans con il parametro n_clusters, che rappresenta il numero di cluster da trovare, e poi chiamare il metodo fit sui dati osservati. Le assegnazioni del cluster possono essere estratte dalla proprieta labels_.

Nel seguente codice questa procedura viene eseguita sia per k=3 che per k=4 sullo stesso set di dati:

				
					# Applico l'algoritmo K-Means per k=3, che è uguale
# al numero dei veri cluster gaussiani
km3 = KMeans(n_clusters=3)
km3.fit(X)
km3_labels = km3.labels_

# Applico l'algoritmo K-Means per k=4, che è uguale
# al numero dei veri cluster gaussiani
km4 = KMeans(n_clusters=4)
km4.fit(X)
km4_labels = km4.labels_
				
			
Infine creiamo due grafici a dispersione dei dati tramite la libreria Matplotlib, uno per k=3 e uno per k=4, utilizzando i colori per rappresentare le assegnazioni dei cluster mediante l’algoritmo K-Means:
				
					# Creo il grafico per confrontare l'algoritmo 
# K-Means per k=3 e k=4 
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14,6))
ax1.scatter(X[:, 0], X[:, 1], c=km3_labels.astype(np.float))
ax1.set_xlabel("$x_1$")
ax1.set_ylabel("$x_2$")
ax1.set_title("K-Means con $k=3$")
ax2.scatter(X[:, 0], X[:, 1], c=km4_labels.astype(np.float))
ax2.set_xlabel("$x_1$")
ax2.set_ylabel("$x_2$")
ax2.set_title("K-Means con $k=4$")
plt.show()
				
			
Di seguito l’output del codice:
trading-machine-learning-clustering-k-means-simulated

Da notare che le differenze di colore tra i due grafici  rappresentano solamente la presenza di un diverso numero di cluster, piuttosto che qualsiasi altra relazione implicita.

L’intero set di dati bidimensionale è stato generato dal campionamento di tre  separate distribuzioni gaussiane con medie e varianze diverse. Risulta subito evidente che la scelta di k quando si esegue l’algoritmo K-Means è importante per l’interpretazione dei risultati.

Nel grafico di sinistra l’algoritmo è costretto a scegliere tra tre cluster. Ha catturato in gran parte i tre  separati gruppi gaussiani, assegnando rispettivamente i colori blu, rosso e verde. Chiaramente ci sono difficoltà a selezionare i cluster più vicini per i punti “periferici” di ciascun cluster che si trovano nelle vicinanze di \(x_1=5,x_2=8\). Questa è una situazione critica per qualsiasi algoritmo di clustering che implica la “sovrapposizione” dei dati.

Ricordiamo che l’algoritmo K-Means è uno strumento di hard clustering, cioè crea un  distinto confine “hard” tra l’appartenenza ad un cluster o ad un’altro, piuttosto che assegnare probabilisticamente l’appartenenza come in un algoritmo di “soft” cluster.

Nel grafico di destra l’algoritmo è costretto a scegliere tra quattro cluster e ha diviso il raggruppamento sul lato sinistro del grafico in due regioni separate (gialla e verde). Tuttavia è noto che questo cluster è stato generato da una singola distribuzione gaussiana e quindi l’algoritmo ha assegnato i dati “in modo errato”. I restanti cluster sul lato destro sono invece “correttamente” identificati .

La scelta di k ha significative implicazioni sull’utilità dell’algoritmo, in particolare per quanto riguarda le applicazioni di trading quantitativo. Di seguito vediamo come la scelta di k può avere un impatto  significativo sull’efficacia della capacità predittiva per i dati finanziari reali.

Clustering OHLC

In questo paragrafo applichiamo il K-Means clustering ai dati giornalieri Open-High-Low-Close (OHLC), noti anche come barre o candele. Questa analisi è interessante perché considera dimensioni extra dei dati giornalieri che sono spesso ignorate, a favore del solo utilizzo di prezzi di chiusura rettificati.

Dato che è importante confrontare ciascuna candela su una base “simile”, dobbiamo normalizzare ciascuna delle dimensioni High, Low e Close con il corrispondente prezzo di apertura. Si ha l’ulteriore vantaggio di rettificare/aggiustare automaticamente i prezzi  in base agli split azionari, ai dividendi e altri eventi aziendali “discreti” che incidono sui prezzi. Normalizzando ciascuna candela con questa logica, si riduce la dimensione da quattro (Aperto, Alto, Basso, Chiuso) a tre (Alto/Aperto, Basso/Aperto, Chiuso/Aperto).

Nel seguente codice scarichiamo due anni di dati  dell’indice S&P500. Rappresentiamo graficamente le candele utilizzando Matplotlib. Quindi normalizziamo  i dati come appena descritto, e li raggruppiamo utilizzando K-Means e quindi  creiamo un diagramma a dispersione tridimensionale per visualizzare l’appartenenza al cluster.
A questo punto applichiamo nuovamente le etichette del cluster ai dati della candela originali e le usiamo per visualizzare l’appartenenza al cluster (così come i confini) su un grafico a candela ordinato in base all’etichetta del cluster. Infine, creiamo una “matrice di follow-on”, che descrive la frequenza che il cluster di domani sia j, se il cluster di oggi è i. Tale matrice è utile per verificare se c’è la possibilità di costruire una strategia di trading predittiva basata sull’appartenenza al cluster di oggi.

Questo codice richiede molte importazioni. La maggior parte di questi sono relative alle opzioni di formattazione richieste da Matplotlib. La libreria standard copy è usata per creare copie complete dei DataFrames in modo che i dati non siano sovrascritte da ogni successiva funzione  grafica. Inoltre, importiamo NumPy e Pandas per la manipolazione dei dati. Infine importiamo il modulo KMeansda Scikit-Learn:

				
					# ohlc_clustering.py

import copy
import datetime

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mplfinance.original_flavor import candlestick_ohlc
import matplotlib.dates as mdates
from matplotlib.dates import (
    DateFormatter, WeekdayLocator, DayLocator, MONDAY
)
import numpy as np
import pandas as pd
import yfinance as yf
from sklearn.cluster import KMeans
				
			

La prima funzione da implementare crea una serie temporale tridimensionale a partire dai dati OHLC dei prezzi finanziari. Ciascuna dimensione rappresenta rispettivamente i prezzi normalizzati per high/open, low/open e close/open. Si eliminano le altre colonne e si restituisce il DataFrame:

				
					
def get_open_normalised_prices(data):
    """
    Restituisce un DataFrame pandas contenenti i prezzi high, low
    e close normalizzati con il prezzo open per i dati OHLC. 
    Cioè si crea le colonne High/Open, Low/Open e Close/Open
    """
    df = data.copy()
    df["H/O"] = df["High"]/df["Open"]
    df["L/O"] = df["Low"]/df["Open"]
    df["C/O"] = df["Close"]/df["Open"]
    df.drop(
        [
            "Open", "High", "Low",
            "Close", "Volume", "Adj Close"
        ],
        axis=1, inplace=True
    )
    return df
				
			

La seguente funzione plot_candlesticks utilizza il metodo candlestick_ohlc di Matplotlib per creare un grafico a candele della serie dei prezzi finanziari fornita come parametro. La maggior parte della funzione consiste nelle specifiche di Matplotlib necessarie per ottenere una corretta formattazione dei dati. I commenti spiegano ogni istruzione in modo dettagliato:

				
					
def plot_candlesticks(data, since):
    """
    Visualizzo il grafico a candele dei prezzi,
    con specifica formatazzione delle date
    """
    # Copio e resetto l'indice del dataframe
    # per usare solo un sottoinsieme dei dati
    df = copy.deepcopy(data)
    df = df[df.index >= since]
    df.reset_index(inplace=True)
    df['date_fmt'] = df['Date'].apply(
        lambda date: mdates.date2num(date.to_pydatetime())
    )

    # Imposto la corretta formattazione dell'asse delle date
    # con Lunedi evidenziato come un punto principale
    mondays = WeekdayLocator(MONDAY)
    alldays = DayLocator()
    weekFormatter = DateFormatter('%b %d')
    fig, ax = plt.subplots(figsize=(16,4))
    fig.subplots_adjust(bottom=0.2)
    ax.xaxis.set_major_locator(mondays)
    ax.xaxis.set_minor_locator(alldays)
    ax.xaxis.set_major_formatter(weekFormatter)

    # Visualizzo il grafico a candele OHLC, dove i giorni positivi
    # sono candele nere e quelli negativi sono rosse
    csticks = candlestick_ohlc(
        ax, df[
            ['date_fmt', 'Open', 'High', 'Low', 'Close']
        ].values, width=0.6,
        colorup='#000000', colordown='#ff0000'
    )
    ax.set_facecolor((1,1,0.9))
    ax.xaxis_date()
    plt.setp(
        plt.gca().get_xticklabels(),
        rotation=45, horizontalalignment='right'
    )
    plt.show()
				
			
La seguente funzione plot_3d_normalised_candles crea un grafico a dispersione di tutte le candele nello spazio tridimensionale, normalizzate al prezzo di apertura. Ogni candela giornaliera è colorata in base all’appartenenza ad un cluster (che viene determinata nei successivi frammenti di codice):
				
					
def plot_3d_normalised_candles(data, labels):
    """
    Visualizza un grafico a dispersione 3D di tutte le candelle
    normalizzate con l'apertura evidenziando i cluster con colori diversi
    """
    fig = plt.figure(figsize=(12, 9))
    ax = Axes3D(fig, elev=21, azim=-136)
    ax.scatter(
        data["H/O"], data["L/O"], data["C/O"],
        c=labels.astype(np.float)
    )
    ax.set_xlabel('High/Open')
    ax.set_ylabel('Low/Open')
    ax.set_zlabel('Close/Open')
    plt.show()
				
			

La funzione successiva plot_cluster_ordered_candles è simile al grafico a candele precedente, dove viene ordinato in base appartenenza al cluster invece che per data. Inoltre, ogni confine di cluster viene visualizzato con una linea tratteggiata blu. La funzione è alquanto complessa a causa, anche in questo caso, alle istruzioni necessarie per la formattazione con Matplotlib.

L’ultima sezione della funzione prevede la creazione di un DataFrame chiamato change_indices. Il suo compito è determinare l’indice in cui si trova un nuovo limite del cluster. Si ottiene ordinando tutti gli elementi in base al loro indice del cluster e quindi utilizzando il metodo diff per ottenere i punti di modifica. Quindi viene filtrato da tutti i valori che non sono uguali a zero, che restituisce un DataFrame costituito da cinque righe, una per ogni limite. Infine si utilizza il metodo axvline di Matplotlib per tracciare la linea blu tratteggiata:

				
					
def plot_cluster_ordered_candles(data):
    """
    Visualizza il grafico a candele ordinato secondo l'appartenza
    ad un cluster con la linea tratteggiata blu che rappresenta
    il bordo di ogni cluster.
    """
    # Imposto il formato per gli assi per formmattare
    # correttamente le date, con Lunedi come tick principale
    mondays = WeekdayLocator(MONDAY)
    alldays = DayLocator()
    weekFormatter = DateFormatter("")
    fig, ax = plt.subplots(figsize=(16,4))
    ax.xaxis.set_major_locator(mondays)
    ax.xaxis.set_minor_locator(alldays)
    ax.xaxis.set_major_formatter(weekFormatter)

    # Ordino i dati in base ai valori dei cluster e ottengo
    # un dataframe che contiene i valori degli indici per
    # ogni variazione dei bordi dei cluster
    df = copy.deepcopy(data)
    df.sort_values(by="Cluster", inplace=True)
    df.reset_index(inplace=True)
    df["clust_index"] = df.index
    df["clust_change"] = df["Cluster"].diff()
    change_indices = df[df["clust_change"] != 0]

    # Visualizzo il grafico OHLC con le candele ordinati in base ai cluster
    csticks = candlestick_ohlc(
        ax, df[
            ["clust_index", 'Open', 'High', 'Low', 'Close']
        ].values, width=0.6,
        colorup='#000000', colordown='#ff0000'
    )
    ax.set_facecolor((1,1,0.9))

    # Aggiungo ogni bordi di cluster come una linea blu trattegiata
    for row in change_indices.iterrows():
        plt.axvline(
            row[1]["clust_index"],
            linestyle="dashed", c="blue"
        )
    plt.xlim(0, len(df))
    plt.setp(
        plt.gca().get_xticklabels(),
        rotation=45, horizontalalignment='right'
    )
    plt.show()
				
			

L’ultima funzione è create_follow_cluster_matrix. Il suo compito è produrre una matrice \(k \times k\), dove k è il numero di cluster selezionati per il processo di K-Means Clustering. Ciascun elemento della matrice rappresenta la frequenza percentuale che il cluster j sia il cluster giornaliero successivo al cluster i. Questo è utile nell’impostazione del trading quantitativo perchè permette di determinare la distribuzione campionaria delle variazioni di cluster.

La matrice viene costruita utilizzando il metodo shift di Pandas, per creare una una nuova colonna ClusterTomorrow contenente il valore del cluster di domani. Si crea quindi la colonna ClusterMatrix formando una tupla dell’indice del cluster di oggi e dell’indice del cluster di domani. Si utilizza quindi il metodo value_counts di Pandas per creare una distribuzione di frequenza di queste coppie. Infine, si calcola una matrice NumPy \(k \times k\) con la frequenza percentuale delle occorrenze di ogni cluster follow-on:

				
					
def create_follow_cluster_matrix(data):
    """
    Crea una matrice k x k, dove k è il numero di clusters
    che mostra quanto il cluster i è seguito dal cluster j.
    """
    data["ClusterTomorrow"] = data["Cluster"].shift(-1)
    data.dropna(inplace=True)
    data["ClusterTomorrow"] = data["ClusterTomorrow"].apply(int)
    data["ClusterMatrix"] = list(zip(data["Cluster"], data["ClusterTomorrow"]))
    cmvc = data["ClusterMatrix"].value_counts()
    clust_mat = np.zeros( (k, k) )
    for row in cmvc.iteritems():
        clust_mat[row[0]] = row[1]*100.0/len(data)
    print("Cluster Follow-on Matrix:")
    print(clust_mat)
				
			
La funzione __main__ unisce tutte le funzioni di cui sopra. Si esegue l’algoritmo K-Means e si usano i valori di appartenenza ai cluster in tutte le successive funzioni:
				
					
if __name__ == "__main__":
    # Scarico i dati dei prezzi di S&P500 da Yahoo Finance
    start = datetime.datetime(2013, 1, 1)
    end = datetime.datetime(2015, 12, 31)
    sp500 = yf.download("^GSPC", start, end)

    # Visualizzo il grafico a candele dei prezzi dell'ultimo anno
    plot_candlesticks(sp500, datetime.datetime(2015, 1, 1))

    # Eseguo il K-Means clustering con 5 clusters sui dati
    # 3-dimensionali di H/O, L/O e C/O
    sp500_norm = get_open_normalised_prices(sp500)
    k = 5
    km = KMeans(n_clusters=k, random_state=42)
    km.fit(sp500_norm)
    labels = km.labels_
    sp500["Cluster"] = labels

    # Visualizzo il grafico 3D delle candele normalizzate usando H/O, L/O, C/O
    plot_3d_normalised_candles(sp500_norm, labels)

    # Visualizzo le candele OHLC riordinate
    # nei loro rispettivi cluster
    plot_cluster_ordered_candles(sp500)

    # Creo e restituisco la matrice dei cluster follow-on
    create_follow_cluster_matrix(sp500)
				
			
Di seguito l’output della matrice di follow-on del cluster:
				
					Cluster Follow-on Matrix:
[[ 2.38410596  2.78145695  4.2384106   3.57615894  1.05960265]
 [ 1.45695364  1.85430464  3.17880795  4.76821192  0.66225166]
 [ 4.2384106   1.98675497  9.93377483 14.43708609  1.05960265]
 [ 5.43046358  4.2384106  12.58278146 14.70198675  1.05960265]
 [ 0.66225166  0.92715232  1.7218543   0.52980132  0.52980132]]
				
			

Da notare che la matrice non è certamente distribuita uniformemente. In altre parole, è probabile che alcune “candele” seguano altre con maggiore frequenza. Questo permette la possibilità di creare strategie di trading attorno all’identificazione dei cluster e alla previsione dei cluster successivi.

La seguente figura mostra il grafico delle candele dei prezzi OHLC dell’S&P500 per tutto il 2015. Da notare il forte ribasso intorno alla fine di agosto e la successiva lenta ripresa in ottobre e novembre:

trading-machine-learning-clustering-ohlc-sp500-candles

La seguente figura è un grafico tridimensionale di prezzi normalizzati high/open, low/open e close/open tracciati l’uno contro l’altro. Ognuno dei k=5 cluster sono stati colorati. Si noti che la maggior parte delle barre si trovano intorno \((1.0, 1.0, 1.0)\). Questo significa che la maggior parte dei giorni non è estremamente volatile e quindi le azioni non vengono scambiate con prezzi in un intervallo troppo ampio.

Tuttavia, ci sono molti giorni in cui il prezzo di chiusura è sostanzialmente al di sopra del prezzo di apertura, come evidenziato dal cluster azzurro nella parte superiore della figura. Inoltre ci sono molti giorni in cui il prezzo minimo è molto al di sotto del prezzo di apertura, indicato dal cluster giallo:

trading-machine-learning-clustering-ohlc-kmeans-scatterplot

La seguente figura mostra le candele per il 2013-2015 ordinate in base all’appartenenza al cluster. Da questo grafico si evidenzia il funzionamento dell’algoritmo K-Means sui dati delle candele. Ci sono due grandi cluster al centro del grafico che rappresentano rispettivamente giorni di leggero ribasso e giorni di leggero rialzo., mentre alle estremità del grafico si possono vedere i rialzi e i ribassi più elevati.

E’ interessante notare come l’appartenenza al cluster è altamente discontinua. Ci sono molti più giorni a bassa volatilità che giorni ad alta volatilità. In particolare i cluster estremi contengo le giornate con forti ribassi:

trading-machine-learning-clustering-ohlc-candle-clusters

Questa analisi è certamente interessante e motiva ulteriori approfondimenti. Tuttavia, è necessaria una notevole quantità di lavoro extra per eseguire qualsiasi forma di strategia di trading quantitativo. In particolare, ci siamo limitato a solo due anni interi dei dati giornalieri dell’S&P500. Potrebbe essere facilmente esteso nel tempo o su molti più asset (azioni o altro).

Inoltre non possiamo verificare se la scelta di k è corretta. Forse \(k=4\) o \(k=6\) potrebbe ro essere migliori. Si dovrebbe scegliere k asset per asset e, in caso affermativo, in base a quale metrica di “bontà”?

Un altro problema è che questi calcoli sono effettuati in-sample . Qualsiasi utilizzo futuro di questo algoritmo come strumento predittivo presuppone implicitamente che la distribuzione dei cluster rimanga simile al passato. Un’implementazione più realistica prenderebbe in considerazione una qualche forma di strumento di clustering “rolling” o “online” che produrrebbe una matrice di follow-on per ciascuna finestra mobile.

Sarebbe necessario che questa matrice non si discostasse troppo frequentemente, altrimenti è probabile che il suo potere predittivo sia scarso, ma abbastanza frequentemente da poter rilevare implicitamente i cambiamenti del regime di mercato. Chiaramente questo motiva molte differenti vie di ricerca!

Nota bibliografica

K-Means Clustering è una tecnica ben nota e descritta in molti libri di testo di machine learning. Un’introduzione relativamente semplice, senza ricorrere a troppa matematica, è descritta da James et al (2013) [1]. Vengono delineate le basi dell’algoritmo e le sue insidie.

I libri di livello universitario di Hastie et al (2009) [2] e Murphy (2012) [3] approfondiscono il “quadro più ampio” degli algoritmi di clustering, inserendoli nel quadro della modellazione probabilistica. Inoltre, mettono K-Means nel contesto con altri algoritmi di clustering come la quantizzazione vettoriale e i modelli di miscele gaussiane. Altri libri che discutono del K-Means clustering includono Bishop (2007) [4] e Barber (2012) [5].

Torna su