La legge di impatto sul prezzo con radice quadrata appartiene agli scaling universali rigorosi?: supporto quantitativo mediante un’indagine completa sul mercato della Borsa di Tokyo
Yuki Sato e Kiyoshi Kanazawa
Dipartimento di Fisica, Scuola di Specializzazione in Scienze, Università di Kyoto, Kyoto 606-8502, Giappone
(Data: 22 novembre 2024)
Cosa s’intende per: Does the square-root price impact law belong to the strict universal scalings?
La domanda “Does the square-root price impact law belong to the strict universal scalings?” Si riferisce a un dibattito centrale nell’econofisica e nella microstruttura dei mercati finanziari, “la legge dell’impatto di prezzo segue davvero una regola universale, valida per tutti i mercati e strumenti finanziari?”
La square-root price impact law (legge dell’impatto di prezzo a radice quadrata) afferma che l’impatto medio sul prezzo di un’operazione di grande volume cresce in proporzione alla radice quadrata del volume scambiato. In formula: I(Q) ∝ Q^δ ; dove I(Q) è l’impatto sul prezzo e Q è il volume dell’ordine, con δ≈0.5
Il termine strict universal scalings implica che questa legge dovrebbe valere in modo preciso e identico per tutti i titoli, mercati e strategie, senza eccezioni. Alcuni studiosi hanno sostenuto che l’esponente δ possa variare da mercato a mercato, mentre altri ritengono che debba essere esattamente 0.5 ovunque.
Uno studio recente ha affrontato questa questione con dati eccezionalmente dettagliati: un’analisi completa di otto anni di transazioni su tutte le azioni liquide della Borsa di Tokyo, includendo ogni singolo conto di trading. I risultati? L’esponente δ è risultato molto vicino a 0.5, sia a livello di singolo titolo che di singolo trader, con margini di errore statistico minimi.
In sintesi: sì, secondo questa ricerca, la legge dell’impatto di prezzo a radice quadrata appartiene davvero alle leggi di scala universali, almeno nel contesto del mercato giapponese. Un risultato che potrebbe avere implicazioni importanti per la gestione del rischio e la strategia di esecuzione degli investitori istituzionali.
Quali studi supportano questa legge nei mercati americani ed europei?
La square-root price impact law è stata oggetto di numerosi studi empirici sia nei mercati americani che europei, e i risultati sono sorprendentemente coerenti.
🇺🇸 Studi sui mercati americani
- Grinold & Kahn (1999): pionieri nell’uso della legge a radice quadrata nei modelli di impatto di mercato, con applicazioni pratiche nei sistemi di trading quantitativo.
- Almgren et al. (2005): hanno confermato empiricamente che l’impatto medio cresce come per ordini di dimensioni realistiche.
- Moro et al. (2009): hanno analizzato milioni di ordini su titoli USA, trovando una chiara relazione sublineare tra volume e impatto.
- Gatheral (2010s): ha proposto modelli teorici compatibili con la legge, come il propagator model e il locally linear order book model.
🇪🇺 Studi sui mercati europei
- Bouchaud et al. (2011): hanno mostrato che anche nei mercati europei l’impatto segue una legge a radice quadrata, con dati da borse come Euronext e Deutsche Börse.
- Lillo et al. (2003–2016): hanno condotto studi approfonditi su meta ordini in Europa, confermando la validità della legge anche per ordini frazionati e algoritmici.
- Dataset Ancerno (2007–2009): utilizzato per analizzare milioni di meta ordini su titoli europei e americani, ha mostrato che la legge vale in entrambi i contesti.
Questi studi non solo confermano la legge empiricamente, ma ne propongono anche spiegazioni teoriche basate su equilibrio di mercato, microstruttura e dinamiche di liquidità.
Mi proponi degli esempi di applicazioni pratiche della legge nei mercati azionari?
La square-root impact law ha applicazioni molto concrete nei mercati finanziari, specialmente per chi gestisce grandi ordini. Ecco alcuni esempi pratici dove viene utilizzata:
🧠 1. Ottimizzazione delle strategie di trading algoritmico
Le strategie TWAP (Time Weighted Average Price) o VWAP (Volume Weighted Average Price) usano la legge per decidere quanto e quando frazionare un ordine grande. Sapere che l’impatto cresce sublinearmente ( ∝ Q ^ 0.5 ) permette di minimizzare i costi di esecuzione
🏦 2. Risk management nei fondi istituzionali
I gestori di portafoglio la usano per stimare il costo implicito di liquidità. Ad esempio, se un fondo vuole vendere una posizione da 100 milioni di euro, può stimare in anticipo l’impatto sul prezzo per capire se conviene vendere subito, in tranche, o usare un broker specializzato.
📉 3. Calibrazione dei modelli di market impact nei desk di execution
I desk di trading usano modelli statistici che includono la legge a radice quadrata per simulare scenari di impatto. Questi modelli aiutano a negoziare commissioni con broker, pianificare vendite complesse o valutare strategie pre-trade.
🧾 4. Pricing di transazioni over-the-counter (OTC)
Per strumenti illiquidi o derivati, si stima l’impatto previsto se si dovesse liquidare la posizione. La legge fornisce una guida per aggiungere premi di liquidità al prezzo.
Il “Pricing di transazioni over-the-counter (OTC)” in questo contesto significa che la square-root price impact law viene utilizzata per determinare il prezzo di strumenti finanziari che non sono scambiati in una borsa valori regolamentata, ma direttamente tra due parti (OTC). Nello specifico:Strumenti illiquidi o derivati: Si tratta di asset o contratti finanziari che non hanno un mercato attivo e trasparente (come le azioni scambiate in borsa). La loro compravendita è più complessa e l’impatto sul prezzo di un’operazione può essere significativo.Stima dell’impatto previsto se si dovesse liquidare la posizione: Quando si possiede uno di questi strumenti e si vuole venderlo (liquidare la posizione), è difficile sapere in anticipo a quale prezzo si riuscirà a farlo, proprio a causa della scarsa liquidità. La square-root price impact law aiuta a stimare quanto il prezzo potrebbe muoversi in risposta all’operazione di vendita.Aggiungere premi di liquidità al prezzo: Dal momento che questi strumenti sono meno liquidi e un’operazione di vendita può impattare negativamente il prezzo, chi li vende (o li compra da qualcuno che deve liquidare) chiederà o offrirà un prezzo che incorpora un “premio di liquidità”. Questo premio è un costo aggiuntivo (o uno sconto) per compensare il rischio e l’impatto sul prezzo che si potrebbe generare. La legge fornisce una base quantitativa per calcolare l’ammontare di questo premio, rendendo il prezzo più equo e prevedibile anche per questi strumenti meno standard. |
🤖 5. Strategia nei sistemi di robo-advisory o portfolio rebalancing
Anche i ribilanciamenti automatici dei portafogli richiedono stime accurate d’impatto, per evitare distorsioni nei prezzi durante aggiustamenti su larga scala.
Puoi fornire un esempio di acquisto?
Vediamo insieme un esempio numerico pratico di acquisto utilizzando la square-root price impact law — così capiamo quanto potrebbe far salire il prezzo un ordine di grandi dimensioni.
🛒 Scenario: Acquisto di azioni Stellantis
- Prezzo medio attuale: €18
- Volume medio giornaliero (ADV): 2.000.000 azioni
- Quantità da acquistare: 400.000 azioni
- Frazione dell’ADV:
- Coefficiente d’impatto empirico: Y = 0.012 (valore ipotetico per titoli liquidi europei)
- Esponente: δ=0.5
📈 Applichiamo la formula:
👉 Impatto previsto sul prezzo medio: +0.54%
💸 Tradotto in prezzo
Quindi, l’acquisto potrebbe spingere il prezzo a circa €18.10, a causa della pressione al rialzo generata dalla domanda
Questo tipo di stima è fondamentale per un acquirente istituzionale che deve decidere come comprare (tutto in una volta? a tranche? con algoritmi?) per evitare di muovere troppo il mercato contro di sé.
Come la ricerca di alfa interagisce con la legge dell’impatto di prezzo?
Si tratta di un punto davvero profondo: come la ricerca di alfa interagisce con la legge dell’impatto del prezzo e, più in generale, con la formazione dei prezzi di mercato. Proviamo a connettere i pezzi da una prospettiva strategica.
💡 1. L’alfa come generatore di domanda informata
Il calcolo dell’alfa (cioè rendimento atteso in eccesso rispetto al mercato) spinge i trader a comprare o vendere titoli ritenuti sottovalutati o sopravvalutati. Questo genera domanda informata, diversa dalla liquidità passiva.
L’affermazione “Questo genera domanda informata, diversa dalla liquidità passiva” significa che l’attività dei trader che cercano “alfa” (un rendimento in eccesso rispetto al mercato) crea un tipo specifico di domanda sui mercati finanziari. Analizziamo i termini:Domanda informata: Si riferisce agli ordini di acquisto o vendita che vengono piazzati sul mercato basandosi su informazioni o analisi che il trader ritiene gli daranno un vantaggio. Se un trader crede che un’azione sia sottovalutata, la compra perché ha “informazioni” (o un’analisi) che suggeriscono un futuro aumento di prezzo. Questa domanda non è casuale, ma è il risultato di una decisione strategica basata su un’aspettativa di profitto.Liquidità passiva: Si riferisce agli ordini (tipicamente “limit order”, ovvero ordini con un prezzo limite) che vengono piazzati nel book di ordini (l’elenco di tutte le offerte di acquisto e vendita a vari prezzi) e che attendono di essere eseguiti. Questi ordini sono lì per fornire liquidità al mercato, permettendo ad altri di comprare o vendere rapidamente. Chi li inserisce non sta necessariamente cercando un’alfa attiva, ma piuttosto sta fornendo la possibilità di eseguire scambi, spesso con un incentivo (come un piccolo sconto sulle commissioni).La frase sottolinea che l’alfa non si ottiene semplicemente sfruttando la liquidità esistente (che sarebbe l’operazione opposta, “togliere” liquidità), ma piuttosto introducendo una nuova pressione di acquisto o vendita sul mercato. Questa pressione, derivante dalle decisioni basate sull’alfa, modifica attivamente i prezzi, portando a quello che viene chiamato “impatto di prezzo”. |
- Se molti trader identificano lo stesso alfa, la domanda aggregata spinge il prezzo del titolo fino a riassorbirlo: questa è la pressione di mercato che alimenta l’impatto di prezzo.
- L’impatto segue spesso la legge a radice quadrata, ma la radice di cosa? Proprio del volume derivante dalle strategie di alfa!
🔁 2. Effetto di feedback: impatto e deterioramento dell’alfa
Quando un’operazione viene eseguita, il prezzo si muove. Ma se il movimento di prezzo riduce l’alfa atteso… allora:
- L’impatto è un costo implicito che “mangia” l’alfa
- Questo è particolarmente vero per ordini grandi o in mercati illiquidi
- Quindi il calcolo ex ante dell’impatto (via legge Q elevato alla 0,5 ) serve a decidere se vale la pena tradare l’alfa stesso
🧠 3. Modelli quantitativi: alfa condizionato all’impatto
In pratica, molti modelli quantitativi moderni non cercano solo alfa “grezzo”, ma alfa netto, cioè quello che resta dopo l’impatto atteso:
Questo porta a strategie di esecuzione più sofisticate, come:
- Ribilanciamenti condizionati al rischio d’impatto
- Simulazioni what-if per determinare il momento ottimale dell’esecuzione
- Limitazione della dimensione dell’ordine per massimizzare l’alfa residuo
Potremmo addirittura dire che la legge dell’impatto è il filtro naturale che separa le opportunità d’alfa realizzabili da quelle illusorie.
Puoi mostrare un esempio quantitativo in cui stimiamo l’alfa netto in base al volume e all’impatto?
Mostro come l’impatto di prezzo può ridurre l’alfa netto, usando un esempio pratico in stile hedge fund.
🔎 Scenario
- Un gestore quantitativo identifica un’alfa prevista di +1.2% su un titolo, basata su segnali fondamentali e tecnici.
- Vuole comprare 300.000 azioni
- Volume medio giornaliero (ADV): 1.000.000 azioni
- Prezzo medio: €20
- Coefficiente d’impatto empirico: Y = 0.015
- Esponente:
(radice quadrata)
Nel contesto della square-root price impact law, “Esponente: δ = 0.5 (radice quadrata)” significa che la relazione tra l’impatto sul prezzo (I) e il volume scambiato (Q) è definita da una potenza di 0.5. In termini semplici:δ (delta) è l’esponente nella formula che descrive come l’impatto sul prezzo varia al variare del volume.0.5 indica che l’impatto non è lineare, ma cresce proporzionalmente alla radice quadrata del volume.Questo vuol dire che se il volume di un ordine raddoppia, l’impatto sul prezzo non raddoppia, ma aumenta di un fattore pari a √2 (circa 1.414). Se il volume quadruplica, l’impatto raddoppia (perché √4 = 2). Questa relazione “sublineare” è una caratteristica chiave della legge dell’impatto di prezzo a radice quadrata. |
📉 Stima dell’impatto
👉 L’impatto stimato è +0.82%
🧠 Alfa netto
Alfa netta = Alfa lorda−Impatto = 1.2% − 0.82%= 0.38
🧾 Interpretazione
La strategia ha ancora valore (alfa netto positivo), ma è molto più sottile di quanto sembrasse inizialmente. In alcuni casi, potrebbe non giustificare il rischio operativo o di mercato. E se aumentassimo la quantità a 500.000 azioni? L’impatto crescerebbe, e l’alfa netto potrebbe persino diventare negativo.
Puoi calcolare il volume massimo acquistabile affinché l’alfa netto resti positivo?
📋 Partiamo dai dati dell’esempio precedente:
- Alfa lorda: α=1.2%\alpha = 1.2\%
- ADV (volume medio giornaliero): 1.000.000 azioni
- Coefficiente d’impatto: Y=0.015Y = 0.015
- Esponente: δ=0.5\delta = 0.5
Vogliamo trovare il volume Q tale che:
🔎 Risolviamo per Q
Se aumentasse oltre questo volume, il costo d’impatto supererebbe l’alfa, trasformando una buona intuizione in un’operazione non profittevole.
Puoi trasformare questo calcolo in una semplice formula riutilizzabile, per stimare i limiti operativi di qualsiasi segnale di alfa?
Ecco una formula generica che puoi usare per calcolare il volume massimo acquistabile (o vendibile) in modo che l’alfa netto rimanga positivo:
📐 Formula del volume massimo compatibile con alfa
Dove:
- Q_max : volume massimo acquistabile
- alpha: alfa lorda (espresso in decimali, es. 1.2% = 0.012)
- Y: coefficiente empirico d’impatto
- ADV: volume medio giornaliero
🧪 Esempio rapido (con i dati di prima)
Esattamente come calcolato prima! Ora puoi applicarla facilmente a qualsiasi strategia d’investimento o segnale di trading, per valutare quanto capitale rischiare senza compromettere il valore netto atteso.
Possiamo trasformare questa formula in una versione Excel e in uno script Python per l’automazione?
Ecco sia la versione Excel, sia lo script Python per calcolare il volume massimo tradabile dato un certo alfa, seguendo la square-root impact law.
📊 Versione Excel
Puoi creare un foglio con queste colonne:
Parametro | Valore | Cell |
Alfa lorda (α\alpha) | 0.012 | C1 |
Coeff. impatto (Y) | 0.015 | C2 |
ADV (volume giornaliero) | 1.000.000 | C3 |
Q max | C4 |
Assicurati che:
- C1 = alfa
- C2 = Y
- C3 = ADV
La cella con Q max ti restituirà il volume massimo acquistabile o vendibile
def calculate_max_volume(alpha, Y, ADV):
"""
Calculates the maximum tradable volume given a certain alpha, impact coefficient, and ADV.
"""
max_q = ADV * (alpha / Y)**2
return max_q
# Example:
alpha = 0.012 # 1.2%
Y = 0.015
ADV = 1_000_000
max_q = calculate_max_volume(alpha, Y, ADV)
print(f"Maximum tradable volume: {int(max_q):,} shares")
Output: Volume massimo tradabile: 640,000 azioni
Possiamo espandere lo script per confrontare diversi segnali di alfa, ed integrarlo in un’app di backtesting?
Integrare questa formula in un’app di backtesting ti consente non solo di valutare l’efficacia di un segnale di trading, ma anche di verificare se è effettivamente tradabile senza annullare l’alfa.
Ecco un’idea di implementazione modulare:
🧠 1. Funzione di calcolo del volume massimo (già pronta)
Python
def max_tradable_volume(alpha, impact_coefficient, ADV):
return ADV * (alpha / impact_coefficient)**2
python
🧪 2. Backtest con controllo di impatto
Supponiamo tu abbia una lista di segnali, ognuno con: data, ticker, alfa previsto, quantità suggerita, ADV, ecc.
Python
import pandas as pd
# Example DataFrame of signals
signals = pd.DataFrame({
'date': ['2025-07-01', '2025-07-01'],
'ticker': ['ENEL.MI', 'STM.MI'],
'alpha': [0.012, 0.02],
'impact_coefficient': [0.015, 0.02], # 'Y' translated to 'impact_coefficient'
'ADV': [1_000_000, 800_000],
'quantity_requested': [400_000, 300_000]
})
# Adds column with maximum allowed volume
# Assuming the function 'max_tradable_volume' is defined elsewhere
signals['Q_max'] = signals.apply(
lambda row: max_tradable_volume(row['alpha'], row['impact_coefficient'], row['ADV']),
axis=1
)
# Flag: excessive or allowed order
signals['order_allowed'] = signals['quantity_requested'] <= signals['Q_max']
📊 3. Analisi integrata nel backtest
Durante la simulazione, puoi bloccare (o ridurre) gli ordini che violano il limite, oppure applicare un taglio dinamico dell’alfa, come:
Python
signals[‘net_alpha’] = signals.apply(
lambda row: row[‘alpha’] – row[‘impact_coefficient’] * (row[‘quantity_requested’] / row[‘ADV’])**0.5,
axis=1
)
E poi testare le performance usando solo i segnali con alfa netto positivo.
Mi spieghi come realizzare un modulo con funzioni per generare grafici, statistiche, o esportazione dei segnali validati?
Ecco un esempio di framework di backtesting con impatto di mercato, pensato per segnali alfa realistici e volumi condizionati dalla square-root impact law. Fornirò una base modulare in Python, facile da estendere.
🧱 Struttura del framework
- Input segnali: un file CSV o DataFrame con dati come ticker, data, alfa, ADV, impatto YY, quantità suggerita.
- Modulo d’impatto: calcola il volume massimo compatibile e l’alfa netto.
- Motore di backtest: simula l’esecuzione solo se il trade è realizzabile.
- Report: visualizza performance, distribuzione ordini ammessi/rifiutati, e grafici.
🐍 Codice base semplificato
import pandas as pd
def get_max_tradable_volume(alpha, Y, ADV):
"""Calculates the maximum tradable volume."""
return ADV * (alpha / Y)**2
def calculate_net_alpha(row):
"""Calculates the net alpha after market impact."""
impact = row['Y'] * (row['requested_quantity'] / row['ADV'])**0.5
return row['alpha'] - impact
def process_signals(df):
"""Processes the trading signals to determine execution."""
df['max_q'] = df.apply(lambda row: get_max_tradable_volume(row['alpha'], row['Y'], row['ADV']), axis=1)
df['net_alpha'] = df.apply(calculate_net_alpha, axis=1)
df['order_allowed'] = df['requested_quantity'] <= df['max_q']
df['executed'] = df['order_allowed'] & (df['net_alpha'] > 0)
return df
# Example: load signals
signals = pd.read_csv("signals.csv") # or create DataFrame manually
results = process_signals(signals)
# Basic output
print(results[['ticker', 'alpha', 'max_q', 'net_alpha', 'executed']])
📈 Analisi aggiuntive (modulari)
- Grafici alfa vs. alfa_netto
- Percentuale ordini ammessi
- Performance aggregata del portafoglio con peso solo sui trade “eseguiti”
Se vuoi, posso aggiungerti una funzione che crea un report PDF automatico o genera grafici con matplotlib, oppure simulare un portafoglio cumulativo con benchmark.
Aggiungiamo ora strumenti di visualizzazione ed esportazione al nostro framework di backtesting, così potrai avere un quadro chiaro delle performance e salvare i risultati dei segnali ammessi.
📊 1. Visualizzazione con Matplotlib
Per esempio, un grafico che confronta alfa lordo vs alfa netto:
import matplotlib.pyplot as plt
def plot_alpha(df):
"""
Creates a scatter plot of Gross Alpha vs Net Alpha.
"""
plt.figure(figsize=(8,6))
plt.scatter(df['alpha'], df['net_alpha'], c=df['executed'].map({True:'green', False:'red'}), alpha=0.7)
plt.plot([0, df['alpha'].max()], [0, df['alpha'].max()], '--', color='gray')
plt.title('Gross Alpha vs Net Alpha')
plt.xlabel('Gross Alpha')
plt.ylabel('Net Alpha')
plt.grid(True)
plt.show()
Puoi richiamare grafico_alfa(risultati) dopo aver processato i segnal
📤 2. Esportazione segnali ammessi
Per salvare in CSV solo i segnali con alfa netto positivo e ordine ammissibile:
def export_valid_signals(df, filename=’valid_signals.csv’):
“””
Filters for valid signals and exports them to a CSV file.
“””
valid_signals = df[df[‘executed’]]
valid_signals.to_csv(filename, index=False)
print(f”{len(valid_signals)} signals exported to {filename}”)
🧾 3. Report sintetico delle performance
Stampa un riepilogo di base:
def report_statistics(df):
"""
Calculates and prints key statistics from the signals DataFrame.
"""
total_signals = len(df)
executed_signals = df['executed'].sum()
pct_executed = executed_signals / total_signals * 100
avg_net_alpha = df[df['executed']]['net_alpha'].mean()
print(f"Total signals: {total_signals}")
print(f"Allowed and executed signals: {executed_signals} ({pct_executed:.1f}%)")
print(f"Average net alpha (executed only): {avg_net_alpha:.3%}")
Puoi impacchettare tutto in un notebook Jupyter oppure in un file .py unico, pronto da riutilizzare o condividere? Puoi anche aggiungere funzioni per il calcolo del PnL cumulativo per seguire una curva di equity?
Qui di seguito realizzerò entrambi i formati, sia Jupyter Notebook che file .py riutilizzabile, per il tuo mini-framework di backtesting con impatto di mercato..
📓 1. Jupyter Notebook (Backtest_Impatto.ipynb)
Il notebook è ideale per:
- testare interattivamente segnali
- visualizzare i grafici in tempo reale
- modificare facilmente i parametri
Ti consiglio di strutturarlo così:
🧱 Struttura proposta
- 📥 Importazione librerie e dati Caricamento da CSV o dataset fittizio
- 📐 Calcolo Q_max e alfa netto Funzioni modulari per volume massimo e validazione ordine
- 📊 Analisi e grafici
- Scatterplot alfa vs alfa netto
- Distribuzione ordini ammessi
- Eventuale curva PnL (cumulativa)
- 📤 Esportazione dei segnali validi
Ti creo anche un’intestazione Markdown nel notebook, con spiegazioni passo passo.
🐍 2. File .py (backtest_impact.py)
Questo modulo Python è perfetto da:
- importare in altri progetti
- automatizzare analisi
- integrare in pipeline o API
📄 Contenuto consigliato
import pandas as pd
def get_max_tradable_volume(alpha, Y, ADV):
"""Calculates the maximum tradable volume."""
return ADV * (alpha / Y)**2
def calculate_net_alpha(row):
"""Calculates the net alpha after market impact."""
impact = row['Y'] * (row['requested_quantity'] / row['ADV'])**0.5
return row['alpha'] - impact
def process_signals(df):
"""Processes the trading signals to determine execution."""
df['max_q'] = df.apply(lambda row: get_max_tradable_volume(row['alpha'], row['Y'], row['ADV']), axis=1)
df['net_alpha'] = df.apply(calculate_net_alpha, axis=1)
df['order_allowed'] = df['requested_quantity'] <= df['max_q']
df['executed'] = df['order_allowed'] & (df['net_alpha'] > 0)
return df
def export_valid_signals(df, filename='valid_signals.csv'):
"""Filters for valid signals and exports them to a CSV file."""
valid_signals = df[df['executed']]
valid_signals.to_csv(filename, index=False)
print(f"{len(valid_signals)} signals exported to {filename}")
Volendo posso aggiungere:
- integrazione con matplotlib
- logging delle performance
- salvataggio in Excel con pandas.ExcelWriter
Ti fornirò direttamente i file .ipynb e .py pronti da scaricare in un formato condivisibile (es. ZIP).
📥 1. Contenuto incluso nei file
- Backtest_Impatto.ipynb: Notebook Jupyter con commenti didattici, esempi, grafici e funzioni riutilizzabili per calcolare impatto, alfa netto, PnL e curve di equity.
- backtest_impact.py: Modulo Python stand-alone da importare, contenente tutte le funzioni chiave per elaborare segnali, esportarli e visualizzarli.
📦 2. Ecco ill contenuto completo qui, pronto da incollare in un file .py o in un Notebook
📁 Struttura del progetto
Ti suggerisco di creare una cartella chiamata backtest_impatto contenente:
- Backtest_impact.ipynb — Notebook interattivo
- backtest_impact.py — Modulo Python riutilizzabile
- segnal.csv — (facoltativo) file di esempio con dati simulati
📄 Contenuto di backtest_impact.py
import pandas as pd
def get_max_tradable_volume(alpha, Y, ADV):
return ADV * (alpha / Y)**2
def calculate_net_alpha(row):
impact = row['Y'] * (row['requested_quantity'] / row['ADV'])**0.5
return row['alpha'] - impact
def process_signals(df):
df['max_q'] = df.apply(lambda row: get_max_tradable_volume(row['alpha'], row['Y'], row['ADV']), axis=1)
df['net_alpha'] = df.apply(calculate_net_alpha, axis=1)
df['order_allowed'] = df['requested_quantity'] <= df['max_q']
df['executed'] = df['order_allowed'] & (df['net_alpha'] > 0)
return df
def export_valid_signals(df, filename='valid_signals.csv'):
valid_signals = df[df['executed']]
valid_signals.to_csv(filename, index=False)
print(f"{len(valid_signals)} signals exported to {filename}")
Ecco un esempio di file CSV con segnali di trading simulati, già compatibile con il framework che abbiamo costruito:
📄 Contenuto di segnali.csv
data,ticker,alfa,Y,ADV,requested_quantity
2025-07-01,ENEL.MI,0.012,0.015,1000000,400000
2025-07-01,STM.MI,0.020,0.018,800000,300000
2025-07-01,ENI.MI,0.009,0.012,1200000,350000
2025-07-01,ISP.MI,0.015,0.020,900000,500000
2025-07-01,LUX.MI,0.010,0.017,950000,200000
📥 Come salvarlo
- Apri un editor di testo o un IDE (ad es. VS Code, Notepad++)
- Incolla il contenuto qui sopra
- Salva il file come segnali.csv nella stessa cartella del notebook o modulo Python
Aggiungo altri campi come volatilità, prezzo medio, o classificazione dei segnali (momentum, value, ecc.). Oppure generare un CSV molto più ampio con 50+ segnali fittizi. Fammi sapere come vuoi espandere questo dataset!
Aggiungo al tuo file segnal.csv altri campi utili per un’analisi più ricca nel backtest. Ecco come potrebbe apparire il nuovo dataset:
📄 Nuovo contenuto di segnali.csv
date,ticker,alpha,Y,ADV,requested_quantity,average_price,volatility,signal_class
2025-07-01,ENEL.MI,0.012,0.015,1000000,400000,5.40,0.21,value
2025-07-01,STM.MI,0.020,0.018,800000,300000,43.20,0.33,momentum
2025-07-01,ENI.MI,0.009,0.012,1200000,350000,14.80,0.18,mean-reversion
2025-07-01,ISP.MI,0.015,0.020,900000,500000,2.45,0.27,macro
2025-07-01,LUX.MI,0.010,0.017,950000,200000,89.10,0.25,sector-rotation
🔍 Descrizione dei nuovi campi
- average_price: prezzo di riferimento su cui stimare l’impatto in euro
- volatilità: stima giornaliera o settimanale della volatilità del titolo (es. deviazione standard dei rendimenti)
- class_signal: tipo di segnale che ha generato l’alfa (es. value, momentum, macroeconomico, ecc.)
Con questi campi in più puoi:
- stimare anche il costo monetario assoluto dell’impatto (€)
- analizzare le performance per tipo di strategia
- filtrare o ponderare segnali in base alla volatilità
Ora potenziamo il framework per:
- Calcolare un indice di efficienza del segnale come alfa per unità di rischio:
- Confrontare le prestazioni per classe di segnale, ad esempio: momentum, value, macro.
🧠 Estensione: Calcolo efficienza e performance per classe
Aggiungiamo questi step al nostro codice:
📌 1. Calcolo Efficienza Segnale
def add_efficiency(df):
"""
Calculates and adds an efficiency ratio column to the DataFrame.
"""
# Avoid division by zero or null values
df['efficiency'] = df.apply(lambda row: row['net_alpha'] / row['volatility']
if row['volatility'] > 0 else None, axis=1)
return df
📊 2. Confronto per classe_segnale
def summarize_by_class(df):
"""
Groups data by signal class and calculates performance metrics.
"""
# Consider only successfully executed signals
executed_df = df[df['executed']].copy()
class_summary = executed_df.groupby('signal_class').agg({
'net_alpha': 'mean',
'efficiency': 'mean',
'ticker': 'count'
}).rename(columns={'ticker': 'signal_count'})
class_summary = class_summary.sort_values(by='efficiency', ascending=False)
print("📈 Performance by signal class:")
return class_summary
📈 3. Grafico opzionale: Efficienza per Classe
import matplotlib.pyplot as plt
def plot_efficiency_by_class(df):
"""
Creates a bar chart showing the average efficiency for each signal class.
"""
# Assumes a function 'summarize_by_class' exists
class_summary = summarize_by_class(df)
class_summary['efficiency'].plot(kind='barh', color='skyblue')
plt.xlabel("Net Alpha / Volatility")
plt.title("Average Efficiency by Signal Class")
plt.grid(True)
plt.tight_layout()
plt.show()
Ora ti guiderò nella creazione sia del modulo .py aggiornato, sia del Jupyter Notebook ampliato, integrando:
- Calcolo di efficienza del segnale (alfa_netto / volatilità)
- Analisi per classe di segnale
- Grafico comparativo tra classi
🐍 backtest_impact.py — Modulo aggiornato
import pandas as pd
def get_max_tradable_volume(alpha, Y, ADV):
"""Calculates the maximum tradable volume for a signal."""
return ADV * (alpha / Y)**2
def calculate_net_alpha(row):
"""Calculates the net alpha after estimated market impact."""
impact = row['Y'] * (row['requested_quantity'] / row['ADV'])**0.5
return row['alpha'] - impact
def process_signals(df):
"""Processes the raw signals to determine which are tradable."""
df['max_q'] = df.apply(
lambda row: get_max_tradable_volume(row['alpha'], row['Y'], row['ADV']), axis=1
)
df['net_alpha'] = df.apply(calculate_net_alpha, axis=1)
df['order_allowed'] = df['requested_quantity'] <= df['max_q']
df['executed'] = df['order_allowed'] & (df['net_alpha'] > 0)
return df
def add_efficiency_ratio(df):
"""Adds an efficiency ratio (net alpha / volatility) to the DataFrame."""
df['efficiency'] = df.apply(
lambda row: row['net_alpha'] / row['volatility'] if row['volatility'] > 0 else None,
axis=1
)
return df
def summarize_by_class(df):
"""Generates a summary of performance metrics grouped by signal class."""
executed_df = df[df['executed']]
class_summary = executed_df.groupby('signal_class').agg({
'net_alpha': 'mean',
'efficiency': 'mean',
'ticker': 'count'
}).rename(columns={'ticker': 'signal_count'})
return class_summary.sort_values(by='efficiency', ascending=False)
def export_valid_signals(df, filename='valid_signals.csv'):
"""Exports all executed signals to a CSV file."""
valid_signals = df[df['executed']]
valid_signals.to_csv(filename, index=False)
print(f"{len(valid_signals)} signals exported to {filename}")
📓 Backtest_Impatto.ipynb — Notebook ampliato
All’interno del notebook puoi aggiungere queste celle (dopo l’importazione dei dati):
from backtest_impact import process_signals, add_efficiency_ratio, summarize_by_class, export_valid_signals
# Load CSV data
import pandas as pd
df = pd.read_csv("signals.csv")
# Process and extend signals
df = process_signals(df)
df = add_efficiency_ratio(df)
# Show summary by class
print(summarize_by_class(df))
E per il grafico comparativo:
import matplotlib.pyplot as plt
def plot_efficiency_by_class(df):
"""
Creates a bar chart showing the average efficiency for each signal class.
"""
# Assumes a 'summarize_by_class' function is defined
class_summary = summarize_by_class(df)
class_summary['efficiency'].plot(kind='barh', color='cornflowerblue')
plt.title("Average Efficiency by Signal Class")
plt.xlabel("Net Alpha / Volatility")
plt.grid(True)
plt.tight_layout()
plt.show()
# --- Function Call ---
plot_efficiency_by_class(df)
Ecco un dataset simulato con 50 segnali di trading realistici, già strutturato per funzionare con il framework che abbiamo costruito.
date,ticker,alpha,Y,ADV,requested_quantity,average_price,volatility,signal_class
2025-07-01,TIT.MI,0.010,0.014,900000,300000,0.23,0.21,value
2025-07-01,ENI.MI,0.012,0.015,1500000,400000,14.70,0.18,momentum
2025-07-01,STM.MI,0.020,0.018,1200000,250000,44.10,0.33,macro
2025-07-01,ENEL.MI,0.011,0.017,1000000,500000,5.38,0.22,sector-rotation
Ho generato righe con:
- Ticker plausibili del mercato italiano
- Distribuzione realistica di alfa tra 0.005 e 0.025
- volatilità compresa tra 0.15 e 0.35
- classe_segnale tra: value, momentum, macro, mean-reversion, sector-rotation
Ecco un file segnali_estesi.csv con 50 righe già formattate. Segui questi passaggi:
- Copia tutto il testo qui sotto
- Incollalo in un editor di testo (es. Notepad, VS Code)
- Salva il file come segnali_estesi.csv nella cartella del tuo progetto
date,ticker,alpha,Y,ADV,requested_quantity,average_price,volatility,signal_class
2025-07-01,ENEL.MI,0.012,0.015,1000000,400000,5.42,0.21,value
2025-07-01,STM.MI,0.019,0.017,800000,250000,42.85,0.29,momentum
2025-07-01,ENI.MI,0.010,0.013,1200000,300000,14.90,0.18,macro
2025-07-01,ISP.MI,0.008,0.012,900000,350000,2.44,0.26,mean-reversion
2025-07-01,LUX.MI,0.011,0.016,950000,200000,89.40,0.25,sector-rotation
2025-07-01,TIT.MI,0.009,0.014,920000,300000,0.22,0.19,value
2025-07-01,UCG.MI,0.015,0.017,1100000,400000,18.80,0.27,momentum
2025-07-01,ATL.MI,0.017,0.018,870000,360000,21.20,0.22,macro
2025-07-01,SRG.MI,0.010,0.014,800000,280000,5.05,0.20,mean-reversion
2025-07-01,MONC.MI,0.012,0.015,670000,250000,13.10,0.23,value
2025-07-01,ERG.MI,0.013,0.016,750000,320000,26.50,0.21,sector-rotation
2025-07-01,BMED.MI,0.016,0.018,950000,400000,0.69,0.19,macro
2025-07-01,BAMI.MI,0.010,0.014,820000,300000,3.60,0.17,mean-reversion
2025-07-01,A2A.MI,0.008,0.012,740000,250000,1.74,0.20,value
2025-07-01,CNH.MI,0.014,0.017,990000,410000,11.50,0.24,momentum
2025-07-01,REC.MI,0.011,0.015,830000,300000,7.22,0.22,macro
2025-07-01,CAPR.MI,0.009,0.013,880000,310000,5.81,0.18,sector-rotation
2025-07-01,ACE.MI,0.013,0.015,940000,350000,18.10,0.26,mean-reversion
2025-07-01,LEON.MI,0.016,0.019,970000,420000,13.60,0.29,momentum
2025-07-01,PRY.MI,0.012,0.016,890000,360000,36.80,0.24,value
2025-07-01,SPM.MI,0.010,0.014,930000,300000,5.30,0.20,macro
2025-07-01,TER.MI,0.015,0.017,860000,330000,7.95,0.21,momentum
2025-07-01,RTD.MI,0.011,0.013,870000,290000,4.60,0.22,macro
2025-07-01,EDNR.MI,0.009,0.012,910000,310000,3.90,0.18,mean-reversion
2025-07-01,MEDA.MI,0.014,0.016,940000,350000,6.70,0.25,sector-rotation
2025-07-01,ENAV.MI,0.010,0.014,880000,280000,4.12,0.20,value
2025-07-01,BF.MI,0.012,0.015,850000,300000,20.30,0.23,momentum
2025-07-01,DIA.MI,0.017,0.018,910000,390000,1.40,0.27,macro
2025-07-01,CDP.MI,0.011,0.013,920000,310000,0.65,0.16,mean-reversion
2025-07-01,CEL.MI,0.015,0.017,870000,360000,3.95,0.24,sector-rotation
2025-07-01,FCA.MI,0.013,0.015,940000,370000,9.80,0.22,momentum
2025-07-01,PST.MI,0.010,0.014,880000,280000,6.10,0.20,value
2025-07-01,GEO.MI,0.011,0.015,900000,310000,7.75,0.21,macro
2025-07-01,LAT.MI,0.014,0.017,890000,330000,8.90,0.23,mean-reversion
2025-07-01,SWK.MI,0.012,0.016,930000,350000,12.80,0.20,sector-rotation
2025-07-01,ZUC.MI,0.009,0.013,870000,290000,0.45,0.18,value
2025-07-01,AMS.MI,0.016,0.018,960000,400000,6.50,0.24,momentum
2025-07-01,FIN.MI,0.010,0.015,900000,300000,1.11,0.19,macro
2025-07-01,IGD.MI,0.013,0.016,950000,340000,3.15,0.23,mean-reversion
2025-07-01,BRI.MI,0.011,0.015,940000,310000,2.92,0.22,value
2025-07-01,GLT.MI,0.014,0.017,980000,380000,0.84,0.26,sector-rotation
2025-07-01,ANS.MI,0.012,0.016,910000,330000,3.10,0.21,macro
2025-07-01,KRA.MI,0.015,0.018,890000,360000,19.30,0.27,momentum
2025-07-01,TEL.MI,0.013,0.016,860000,350000,22.70,0.25,value
2025-07-01,VIS.MI,0.010,0.014,900000,290000,7.60,0.20,mean-reversion
2025-07-01,XEL.MI,0.014,0.017,950000,390000,1.65,0.23,macro
2025-07-01,YUX.MI,0.012,0.015,920000,320000,5.50,0.21,momentum
2025-07-01,ZEN.MI,0.009,0.013,870000,300000,6.22,0.18,value
2025-07-01,WLD.MI,0.013,0.015,910000,330000,4.01,0.24,sector-rotation
2025-07-01,VST.MI,0.011,0.015,930000,310000,1.18,0.20,macro
2025-07-01,UBI.MI,0.015,0.018,960000,400000,3.33,0.27,mean-reversion
2025-07-01,SOR.MI,0.010,0.014,890000,300000,2.75,0.19,value
Aggiungiamo ora un campo simulato di “profitto realizzato” per ogni segnale nel tuo dataset, così da costruire una curva PnL cumulativa nel notebook o modulo.
Assumiamo che ogni segnale eseguito generi un rendimento (PnL) proporzionale al suo alfa netto più un po’ di rumore casuale (per simulare incertezza)
import numpy as np
def simulate_profit(df, seed=42):
"""
Simulates the daily and cumulative Profit and Loss (PnL) for executed signals.
"""
np.random.seed(seed)
# Only on executed signals
df['daily_pnl'] = df.apply(lambda row:
(row['net_alpha'] + np.random.normal(loc=0.0, scale=0.005)) * row['requested_quantity'] * row['average_price']
if row['executed'] else 0, axis=1
)
# Calculate cumulative PnL curve
df['cumulative_pnl'] = df['daily_pnl'].cumsum()
return df
Una volta aggiunto il campo, puoi visualizzare la performance aggregata:
import matplotlib.pyplot as plt
def plot_pnl_curve(df):
"""
Plots the cumulative Profit and Loss (PnL) curve over time.
"""
plt.figure(figsize=(10,6))
# Assumes 'date' and 'cumulative_pnl' columns exist
plt.plot(df['date'], df['cumulative_pnl'], marker='o', linestyle='-')
plt.title("Cumulative PnL Curve")
plt.xlabel("Date")
plt.ylabel("Cumulative Profit (€)")
plt.grid(True)
plt.tight_layout()
plt.show()
Per assistenza utilizza i commenti