In questo blog pubblico un articolo che ho scritto per partecipare al concorso “SIAT Technical Analyst of the Year 2021“. Vista la lunghezza e la quantità degli argomenti trattati, ho ritenuto utile dividerlo in quattro parti. In questa seconda parte, tratteremo le serie storiche sintetiche e della loro importanza per creare un modello predittivo del massimo drawdown.
Creare Serie Storiche Sintetiche
Uno dei principali colli di bottiglia che gli analisti incontrano è la sfida di avere abbastanza dati. Se abbiamo la necessità di dati storici per testare le nostre strategie, o abbiamo la necessità di maggiori dati per addestrare gli algoritmi di Machine Learning per realizzare modelli migliori, l’uso dei dati sintetici può essere la risposta.
Come la parola suggerisce, i dati sintetici sono prodotti artificialmente, con il vincolo di mantenere le stesse caratteristiche dei dati raccolti nella realtà. L’impiego di dati sintetici sta incredibilmente crescendo, aziende come Amazon hanno rivolto l’attenzione ai dati sintetici per generare quell’ampia quantità di dataset richiesti per addestrare Alexa a nuove lingue(1), oppure Google ha avviato un progetto per creare immagini mediche sintetiche per generare modelli che aiutino i medici nella diagnosi(2).
Adottare un approccio statistico allo studio del DrawDown porta inevitabilmente con sé l’esigenza di disporre di serie storiche che siano sufficientemente ampie temporalmente ma soprattutto in numero elevato per coprire ogni casistica.
Le dodici serie storiche scelte in precedenza non sono sufficienti per una accurata analisi statistica e soprattutto per addestrare un algoritmo di Machine Learning, dunque la possibilità di adottare delle serie storiche sintetiche è conveniente e ai giorni nostri è anche economica, a differenza di qualche anno fa quando erano richiesti programmi informatici sofisticati e persone che li sapessero usare. Servirsi delle serie storiche sintetiche è una opportunità che non ci facciamo sfuggire.
Il metodo che useremo per creare le serie storiche si basa sostanzialmente sul processo di Wiener, usato anche per generare un moto browniano, il quale è in grado di restituire una equity line stocastica che abbia le caratteristiche statistiche di media e deviazione standard fissate a priori. La sua formulazione generale è la seguente:
Yi = Yi-1 ∙ ( 1 + μ * ( ti – ti-1 ) + σ * radq( ti – ti-1 ) * Xi )
Dove:
- ti è l’i-esimo instante temporale;
- Yi è il valore della Equity all’istante temporale ti;
- Xi è una variabile casuale indipendente con distribuzione normale standard;
- μ e σ sono i parametri caratteristici del processo di Wiener;
Nel nostro caso, assumiamo le seguenti ipotesi:
- Le serie storiche sintetiche hanno un timeframe giornaliero, mentre gli intervalli temporali ti-ti-1 sono sempre costanti e uguali ad 1;
- μ e σ sono rispettivamente il rendimento medio atteso e la deviazione standard.
La formula assume la seguente espressione:
Yi = Yi-1 + Yi-1 ∙ ( μ + σ Xi )
Dove il termine tra parentesi è il rendimento nominale del giorno i:
ri = μ + σ Xi
Se si decidesse di utilizzare il rendimento logaritmico, è possibile sfruttare le proprietà dei logaritmi e trovare i rendimenti medi, parziali o totali con delle semplici sommatorie. Pertanto, se definiamo il rendimento ri come logaritmico, troveremo che il Ri è il rendimento logaritmico cumulato al giorno i-esimo:
Ri = ln( Yi / Y0 ) = ∑(k=1,i) rk = ∑(k=1,i) ( μ + σXk )
Il valore della Equity Yi diventa:
Yi = Y0 * e∑(k=1,i)(μ+σXk)
Un esempio può aiutare a comprendere come funzioni questa formula. Fissato σ=10%, μ=5% e Y0=100€, troviamo:
| ti | Xi | ri = μ + σXi | Ri = ∑(k=1,i)rk | Yi = Y0 ∙ eRi |
|---|---|---|---|---|
| 1 | 0.5 | 12.5% | 12.5% | 113€ |
| 2 | -0.2 | 9.0% | 21.5% | 124€ |
| 3 | 0.1 | 10.5% | 32.0% | 138€ |
| 4 | 0.0 | 10.0% | 42.0% | 152€ |
Dove Xi sono numeri casuali con distribuzione normale standard e Yi è la equity line sintetica.
Il modello presentato è stato implementato come segue:
- Per prima cosa, si fissa il numero delle serie storiche che andranno a comporre il dataset e il numero di epoche per ogni campione. In questa occasione, si è scelto 2048 serie storiche e 20°000 epoche, numeri alti ma che garantiscono un buon addestramento e una buona accuratezza statistica.
- Successivamente, si determinano gli intervalli per la media e la deviazione standard dei rendimenti. Per confrontare questo dataset con quello dei dati reali, si sono scelti due intervalli che contengano le serie storiche elencate nella tabella 2:
- μ = ( 0.005% – 0.035% )
- σ = ( 0.05% – 1.50% )
- Per studiare la possibile relazione del MDD e del periodo, si è scelto di far variare anche la lunghezza delle epoche di ogni serie, con un periodo che ricade all’interno dell’intervallo 8 – 32 valori.
- Per ognuna delle 2048 serie storiche sintetiche, il codice determina in modo casuale media, deviazione standard e periodo all’interno degli intervalli forniti, successivamente calcolerà 20°000 equity line e i relativi 90MDD.
- • I risultati trovati sono salvati all’interno di un database, che chiameremo dataset dei dati sintetici.
In allegato è disponibile il codice, sia per consultazione sia per chi volesse testarlo in proprio, mentre nella tabella 4 si riporta un estratto dei risultati presenti nel dataset.
I parametri scelti fanno già intendere che la mole di dati sintetici non è minimamente confrontabile con quella reale. Disporre di 2048 serie storiche con caratteristiche molto differenti è un qualcosa che è difficile da trovare nei mercati finanziari mondiali. Nella figura 6 sono riportate le posizioni di 512 serie storiche sintetiche (un quarto dell’intero dataset) all’interno del diagramma rischio – rendimento e, per confronto, sono presenti anche le posizioni delle dodici serie storiche reali. La diffusione sul grafico è capillare, si ricopre uno spettro quasi completo di tutte le combinazioni di rischio e rendimento.
| Serie Storica | Media dei rendimenti | Dev.St. dei rendimenti | Periodo dell’epoca | 90MDD |
|---|---|---|---|---|
| 1 | 0,0307% | 1,39% | 15 | 8,10% |
| 2 | 0,0097% | 0,37% | 20 | 2,60% |
| 3 | 0,0325% | 0,48% | 31 | 3,94% |
| 4 | 0,0052% | 1,47% | 13 | 8,13% |
| … | … | … | … | |
| 2048 | 0,0164% | 0,90% | 10 | 4,22% |

Aver scelto 20°000 epoche lunghe mediamente 20 valori significa avere 400°000 valori, corrispondenti a ben 16 secoli! Può sembrare una esagerazione però permette di trovare una ottima accuratezza. Il confronto tra la figura 3 e la figura 7 è esplicativo di questo vantaggio, il numero elevato di epoche permette di avere un’ottima approssimazione della distribuzione di probabilità dei rendimenti cumulati Ri e del MDD, rispettivamente alla gaussiana e alla log-normale (linee arancioni), difficilmente realizzabile con serie storiche reali.

per una generica serie storica sintetica
Le serie storiche sintetiche risolvono in un solo colpo i limiti delle serie reali, poche righe di codice possono generare numeri elevatissimi di informazioni. Ci può essere dello scettiscismo su questa tipologia di serie storica che nasce in laboratorio, ma questi prodotti non sono il fine bensì il mezzo per addestrare un algoritmo di Machine Learning.
(1) https://www.amazon.science/blog/tools-for-generating-synthetic-data-helped-bootstrap-alexas-new-language-releases
(2) https://ai.googleblog.com/2020/02/generating-diverse-synthetic-medical.html
Allegato
Si riporta di seguito un estratto dei codici utilizzati per realizzare le analisi numeriche.
Importazione delle librerie:
from scipy.stats import norm
import numpy as np
import pandas as pd
import sqlite3 as sql
definizione del numero dei campioni e delle epoche, creazione dei DataFrame usati per registrare i risultati:
samples = range(1, 2048 + 1)
epoch = range(1, 20000 + 1)
DATASET = pd.DataFrame(index = samples, columns = ['media', 'devst', 'periodo'])
TARGET = pd.DataFrame(index = samples, columns = ['MDD'])
Doppio ciclo iterativo, in quello esterno si definiscono casualmente le caratteristiche di media, deviazione standard e periodo dell’epoca all’interno di un intervalli fissati, in quello interno si calcola l’equity e il 90° percentile del MDD:
for j in samples :
Risultato = np.zeros( ( len(epoch) ) )
media = np.random.uniform(low = 0.00005, high = 0.00035)
devst = np.random.uniform(low = 0.0005, high = 0.0150)
periodo = np.random.randint(8, high = 32, size = None, dtype = int)
for i in epoch :
LogReturn = norm.rvs(loc = media, scale = devst, size = periodo)
cumulato = np.cumsum(LogReturn)
Equity = 1000 * np.exp(cumulato)
DD_rif = np.maximum.accumulate(Equity)
DrawDown = (DD_rif - Equity) / DD_rif
Risultato[i-1] = DrawDown.max()
DATASET.loc[j, 'media'] = media
DATASET.loc[j, 'devst'] = devst
DATASET.loc[j, 'periodo'] = periodo
TARGET.loc[j, 'MDD'] = np.percentile(Risultato, 90)
Salvataggio dei risultati all’interno di un Database:
conn = sql.connect('Synthetic.sqlite')
cur = conn.cursor()
cur.execute( 'CREATE TABLE IF NOT EXISTS dataset (
"indice" INTEGER NOT NULL UNIQUE,
"media" REAL,
"devst" REAL,
"periodo" REAL,
"MDD" REAL,
PRIMARY KEY ("indice" AUTOINCREMENT) );' )
for i in range(DATASET.shape[0]) :
cur.execute( 'INSERT INTO dataset
(media, devst, periodo, MDD) VALUES (?, ?, ?, ?)',
(DATASET.iloc[i, 0],
DATASET.iloc[i, 1],
int(DATASET.iloc[i, 2]),
TARGET.iloc[i, 0]) )
conn.commit()
cur.close()
Se hai domande o curiosità da chiedermi, non esitare a scrivermi nel modulo qui in basso, oppure mi trovi su Facebook e su LinkedIn. Se vuoi rimanere sempre aggiornato e non perderti nessun articolo appena esce, iscriviti alla newsletter compilando il seguente modulo:
Se apprezzi i miei articoli e il progetto MP Investit, puoi sostenermi con un contributo libero tramite PayPal o con una ricompensa del browser Brave. Questo mi motiva e mi incentiva a produrre contenuti sempre migliori. Grazie!

