Python nel trading: analisi dei dati, automazione e ricerca
Python aiuta a organizzare i dati di mercato, analizzare un diario di trading e ripetere calcoli senza copiare manualmente le formule. È particolarmente utile quando un foglio di calcolo diventa troppo piccolo o difficile da mantenere. Questo articolo accompagna dalla configurazione dell'ambiente a uno script che carica i dati, ne verifica la qualità e salva i risultati. Spiega inoltre la differenza fra ricerca in un notebook e software collegato a un conto di trading, e le protezioni richieste dall'integrazione.

- Python nella ricerca e Python nell'esecuzione comportano due livelli di responsabilità diversi: un notebook che calcola statistiche può arrestarsi senza conseguenze. Il codice che interagisce con il conto deve avere limiti, registro, timeout, modalità di test e arresto controllato
- L'ambiente deve essere riproducibile: versione di Python, ambiente virtuale isolato, versioni delle librerie bloccate, fonte dei dati, fuso orario e intervallo di date. Senza queste informazioni, «sul mio computer funziona» non è un risultato, ma un aneddoto
- pandas e numpy sostituiscono il foglio di calcolo nell'analisi OHLCV: ricampionamento da M1 a H4, media mobile, drawdown, correlazioni fra coppie. Il foglio termina a 1.048.576 righe; i dati tick iniziano ben oltre
- Chiavi e password non vanno mai nel codice: soltanto in una variabile d'ambiente o in un file di configurazione fuori dal repository. Una chiave inserita in uno script finisce nella cronologia Git e vi resta dopo ogni «eliminazione»
- Un'API non è accesso illimitato: ci sono autenticazione, limite alle richieste e codici di errore; l'ambiente di test è talvolta, ma non sempre, su un host separato. Il codice senza gestione di HTTP 429 e senza timeout prima o poi verrà interrotto nel momento peggiore
- Il look-ahead è un solo spostamento nel codice: una posizione decisa alla chiusura della candela agisce soltanto sul rendimento di quella successiva. Moltiplicare il segnale per il rendimento della stessa riga produce un risultato mai fornito dal mercato
- Il codice non eseguito è codice non verificato: compilare e «sembrare corretto» non equivale a eseguire sui dati. Sei blocchi eseguibili sono stati verificati offline con dati e mock dichiarati. I blocchi di output mostrano risultati effettivi di quei test; i comandi d'installazione descrivono la configurazione dell'ambiente, mentre l'invio degli ordini resta uno schema strutturale esplicitamente non eseguibile
1. Dove Python aiuta davvero e dove sembra soltanto una soluzione
Un foglio di calcolo gestisce un diario di trading finché lo consente la pazienza. Il confine dipende dal computer, dal numero di formule e dalla struttura del foglio: non si può descriverlo con un numero tondo di operazioni. Un limite, però, è fisso e indipendente da tutto, perché è scritto nella specifica del formato: un singolo foglio Excel contiene 1.048.576 righe e 16.384 colonne [16]. Un anno di storico tick di una sola coppia valutaria è un multiplo di quel numero, e nessun trucco aggira il limite.
Python non è magia: risolve quattro problemi specifici che un foglio non risolve affatto o risolve al costo di ore di lavoro:
- Scala dei dati: il limite è la memoria del computer e il formato di archiviazione, non il numero di righe di un foglio
- Ripetibilità: lo stesso script eseguito domani sugli stessi dati dà lo stesso risultato. Un foglio con formule trascinate a mano non lo garantisce
- Cambiare intervallo senza lavoro manuale: aggregare da M1 a H4 preservando OHLCV richiede una sola chiamata a
resampleanziché una colonna di formule ausiliarie - Automatizzare l'acquisizione e la pulizia dei dati: uno script pianificato scarica, verifica e archivia i dati senza richiedere la vostra presenza
Tutto qui. Python non offre un vantaggio sul mercato, non migliora la qualità della strategia e non cambia le condizioni di esecuzione presso il broker. Accorcia la distanza fra domanda e risposta: proprio per questo mostra più rapidamente che un'idea non funziona.
Cosa non fa Python. Non è adatto al trading a latenza ultra-bassa: contano i microsecondi e un linguaggio interpretato con garbage collector è una scelta poco adatta. Ma è altrettanto falsa l'affermazione «Python è troppo lento per il trading»: per una strategia che entra all'apertura della successiva candela H1, il tempo di calcolo in Python è trascurabile rispetto al transito dell'ordine attraverso la rete e il server del broker.
L'accelerazione dovuta alla vettorizzazione ha un meccanismo concreto: un'operazione su un'intera colonna viene eseguita in codice numpy compilato anziché in un ciclo dell'interprete. L'ordine di grandezza della differenza dipende dai dati, dall'operazione e dall'hardware. Per citare un numero specifico, misurarlo sul proprio computer con il timeit modulo della libreria standard [4] e registrarlo insieme alle versioni delle librerie. Citare un benchmark altrui senza l'ambiente non è una misurazione.
Confronto degli strumenti analitici del trader:
| Caratteristica | Excel | Python | Pine Script | MQL4/MQL5 |
|---|---|---|---|---|
| Limite dei dati | 1.048.576 righe per foglio | Limitato dalla memoria del computer | Dipende dal piano TradingView | Storico fornito dal broker |
| Dove viene eseguito il codice | In locale | In locale, su un server o nel cloud | Nell'ambiente TradingView | Nel terminale MetaTrader |
| Accesso al conto | Nessuno | Solo tramite API del broker o pacchetto intermediario | Nessuno: un avviso può attivare una catena successiva | Nativo nel terminale |
| Fonte dei dati | Qualsiasi dato caricato | Qualsiasi fonte, ma va dichiarata | Flusso dati TradingView | Flusso dati del broker |
| Riproducibilità | Bassa (formule manuali) | Alta se si bloccano le versioni | Media (versione Pine e dati della piattaforma) | Media (build del terminale e dati del broker) |
| Curva di apprendimento | Bassa | Media | Da bassa a media | Da media ad alta |
La distinzione nell'ultima riga della tabella conta più di quanto sembri. Pine Script viene eseguito nell'ambiente TradingView, MQL nel terminale MetaTrader e Python ovunque lo si avvii. Questa libertà comporta la responsabilità dell'ambiente: versioni delle librerie, fuso orario del sistema, continuità operativa e autorizzazione dello script a interagire con il conto.
openpyxl permette di leggere e scrivere file .xlsx direttamente da Python; la versione più recente risale a giugno 2024 (3.1.5) [13] e in questo caso non è un segnale d'allarme: il formato .xlsx è consolidato e la libreria non dipende né da NumPy né da pandas. La regola della sezione 2 non la esclude, richiede soltanto di conoscere questo stato [STATO AL: 2026-08-22]. Python estende il foglio dove il foglio finisce.2. Ambiente riproducibile: versioni, venv, requirements
Molto spesso uno script altrui «non funziona» non per il codice, ma perché l'autore lo ha eseguito con versioni diverse delle librerie senza registrarle. Nell'analisi dei dati di mercato non è un inconveniente: invalida il risultato, perché non è riproducibile.
Un ambiente è riproducibile quando si possono indicare sei elementi: versione di Python, metodo di isolamento, versioni bloccate delle dipendenze, fonte dei dati con intervallo di date, fuso orario e seme casuale ovunque il risultato dipenda dalla casualità. Un elemento mancante rende il risultato un racconto, non una misurazione.
L'ambiente virtuale non è facoltativo. Installare le librerie «globalmente» fa sì che aggiornare un progetto ne comprometta un altro. L'isolamento è gratuito, richiede una dozzina di secondi e si basa sul venv modulo della libreria standard [4]:
# Windows, PowerShell
py -3.12 -m venv .venv
.venv\Scripts\Activate.ps1
# macOS / Linux
python3.12 -m venv .venv
source .venv/bin/activate
# comune ai due sistemi
python -m pip install --upgrade pip
python -m pip install pandas numpy matplotlib requests python-dotenv
python -m pip freeze > requirements.txt
L'ultima riga è quella più spesso saltata. pip freeze registra le versioni esatte di tutto ciò che è stato effettivamente installato, comprese dipendenze sconosciute. Il file va nel repository accanto al codice. Per ricreare l'ambiente su un altro computer si usa python -m pip install -r requirements.txt, non si procede per tentativi.
L'ambiente utilizzato per la verifica offline attuale degli esempi di calcolo nelle sezioni 4 e 9: Windows 11, Python 3.12.10, pandas 3.0.5 e NumPy 2.5.3, verificato l'8 ottobre 2026. L'elenco seguente registra i pacchetti associati a quei calcoli: è un inventario limitato, non l'esportazione di tutti i pacchetti installati sul computer[1][3].
# Versioni delle dipendenze della verifica di calcolo locale
numpy==2.5.3
pandas==3.0.5
python-dateutil==2.9.0.post0
six==1.17.0
tzdata==2026.4
# interprete
Python 3.12.10
Si noti che pandas utilizza altri pacchetti: la descrizione dell'ambiente non finisce con la versione di una libreria. Le dipendenze possono causare differenze fra computer. I comandi precedenti illustrano la configurazione di un progetto, non registrano un'installazione svolta durante questa verifica. Creare il file completo requirements.txt nel proprio ambiente con python -m pip freeze e conservarlo con il codice.
Neppure questo file descrive l'ambiente dell'intero articolo, ma soltanto quello delle sezioni di calcolo, e così va interpretato. Il frammento che verifica la gestione degli errori HTTP nella sezione 7 è stato eseguito in un ambiente con l'aggiunta di requests 2.34.2; il test della barriera di sicurezza nella sezione 8 non richiede pacchetti esterni alla libreria standard. Le versioni di matplotlib e python-dotenv non sono indicate perché non sono state usate in alcun esempio effettivamente eseguito: elencarle «per ordine» significherebbe scrivere un numero non verificato. Per costruire l'ambiente completo, usare il comando precedente come punto di partenza e bloccare le proprie versioni con pip freeze; è l'unico file delle dipendenze che dice qualcosa sui propri risultati.

Notebook o file .py? Entrambi, ma per scopi diversi. Jupyter è utile quando non si sa ancora cosa cercare: si esegue una cella, si guarda il risultato, si cambia un parametro. Una proprietà lo rende però inadatto al codice di esecuzione: lo stato delle variabili dipende dall'ordine di esecuzione delle celle, non dall'ordine scritto nel file. Un notebook eseguito nell'ordine 1–3–2 appare identico a uno eseguito in sequenza. Il codice che interagisce con il conto deve essere un normale file .py eseguito dall'inizio alla fine, con test e registro.
Anaconda e Google Colab sono scorciatoie con costi propri. Anaconda installa molti pacchetti insieme e ha un proprio gestore delle dipendenze: facilita l'inizio, ma rende più difficile descrivere precisamente l'ambiente se si mescola conda con pip. Colab funziona nel browser senza installazione ed è adatto all'apprendimento, ma l'ambiente viene ricreato da zero a ogni sessione e le versioni delle librerie cambiano senza intervento dell'utente. Per materiale ripetibile fra un anno, entrambi richiedono disciplina aggiuntiva.
Librerie per iniziare: quattro voci, non quaranta (i pacchetti sono cinque: la prima voce è una coppia):
pandasenumpy: strutture dati e calcolomatplotlib: graficirequests: comunicazione HTTP con un'APIpython-dotenv: caricamento della configurazione da un file fuori dal repository
Aggiungere gli indicatori tecnici quando servono davvero. Una correzione alle guide più vecchie: TA-Lib pubblica su PyPI wheel binarie ufficiali con la libreria C; per una combinazione supportata di sistema operativo, architettura e Python basta quindi un semplice pip install TA-Lib senza download non ufficiali né compilazione autonoma[9]. Verificare la wheel specifica prima dell'installazione: non è una promessa di funzionamento su ogni computer. I metadati verificati l'8 ottobre 2026 indicano TA-Lib 0.8.1 e pandas-ta ancora alla versione 0.4.71b0, pubblicata a settembre 2025[13]. Quest'ultima blocca numba==0.61.2, quindi verificare il solo requisito pandas non basta. Un test storico di installazione su Python 3.14.3, datato 22 agosto 2026, è fallito su quella dipendenza con il messaggio only versions >=3.10,<3.14 are supported. L'osservazione non si applica automaticamente agli esempi attuali su Python 3.12.10. Questa verifica ha letto i metadati PyPI, senza installare né eseguire TA-Lib o pandas-ta. La regola di controllo accanto è quindi troppo ristretta: una pubblicazione recente e un requisito pandas compatibile non dimostrano che tutte le dipendenze si possano installare ed eseguire correttamente. Verificare anche le librerie compilate bloccate, la disponibilità di wheel per il proprio interprete e un test su pochi dati. I metadati delle ultime due pubblicazioni non descrivono l'intera storia del progetto: valutare le interruzioni precedenti nel repository.
Il seme casuale. Ovunque il risultato dipenda dalla casualità, dalla divisione del campione al bootstrap, alla simulazione o all'inizializzazione del modello, impostare e registrare il seme. In numpy: rng = np.random.default_rng(20260822) [3], poi usare soltanto quell'oggetto. Senza, due esecuzioni dello stesso script producono risultati diversi e non è chiaro quale venga descritto.
3. pandas e numpy: dati OHLCV, indice temporale, fusi orari
numpy gestisce il calcolo su array numerici, pandas la struttura: un DataFrame è un array con colonne nominate e un indice, di solito temporale. Nei dati di mercato l'indice temporale conta più dei calcoli stessi, perché determina se si stia davvero calcolando ciò che si pensa.
Caricamento – pd.read_csv() gestisce file di milioni di righe, riconosce i separatori e interpreta le date se si indica la colonna. Per impostazione predefinita, però, non sa nulla del fuso orario: è la prima cosa da chiarire.
Cambio di intervallo: aggregare da M1 a H4 preservando OHLCV richiede una sola chiamata: df.resample('4h').agg({'open':'first', 'high':'max', 'low':'min', 'close':'last', 'tick_volume':'sum'}). Attenzione a due argomenti che nessuno imposta e che decidono l'interpretazione: label determina se la finestra è etichettata con l'inizio o la fine, mentre closed determina quale estremo dell'intervallo appartiene alla finestra. Per la maggior parte delle frequenze, l'etichetta predefinita è sul bordo sinistro [1], quindi il timestamp della candela indica l'apertura; in pandas 3.0.5, resample('4h') su otto candele orarie da mezzanotte produce le etichette 00:00 e 04:00. Se la convenzione del segnale presuppone la chiusura mentre i dati sono etichettati con l'apertura, si sposta l'intero risultato di un intervallo senza accorgersene.
Statistiche mobili – df['close'].rolling(20).mean() fornisce una media mobile, .rolling(20).std() la deviazione utilizzata per le bande di Bollinger, .ewm(span=20).mean() una media esponenziale [1]. Tutte sono calcolate esclusivamente da dati passati rispetto alla riga corrente: è la proprietà su cui si fonda la correttezza di tutto il resto.
Filtraggio: una condizione composta si scrive una volta sola: df[(df['sma20'] > df['sma50']) & (df['rsi'] < 30)]. Le parentesi intorno a ogni condizione sono obbligatorie, perché & ha precedenza sui confronti in Python: ometterle produce un errore anziché una risposta sbagliata silenziosa, e qui è una buona notizia.
Il fuso orario è una decisione, non un dettaglio. I dati del broker hanno solitamente il timestamp del suo server, quelli di fornitori esterni sono spesso in UTC e il sistema operativo può usare l'ora locale con i cambi previsti dal fuso scelto. Mescolare questi tre orologi fa sì che un'analisi della «sessione di Londra» studi qualcosa di diverso.
La regola pratica è stabilire prima il fuso d'origine e calcolare in UTC. Per timestamp con offset esplicito o Z usare pd.to_datetime(column, utc=True). Per timestamp del server senza offset, prima interpretarli, poi assegnare il fuso confermato con tz_localize() gestendo le ore ambigue e inesistenti, poi applicare tz_convert("UTC"). Usare soltanto utc=True tratta i valori privi di fuso come UTC, non deduce il fuso del broker. Convertire in Europe/London solo per la presentazione[1]. Un indice con fuso esplicito e una fonte dei dati registrata consentono di combinare le serie coerentemente.
Dati mancanti. Lo storico OHLCV ha lacune: fine settimana, festività, pause tecniche, strumenti non quotati. pandas offre df.ffill(), che riempie le lacune con l'ultimo valore noto: nei prezzi è di solito una cattiva idea, perché crea candele a volatilità zero e abbassa la deviazione standard. Meglio rimuovere i periodi mancanti e contarli.
df.fillna(method='ffill'), comune nelle guide di qualche anno fa, è stata ritirata: il parametro method di fillna() è stato dichiarato deprecato in pandas 2.1 [2] e rimosso in pandas 3.0. È una differenza pratica: la vecchia forma non stampa più un avviso, ma termina il programma con il messaggio TypeError: NDFrame.fillna() got an unexpected keyword argument 'method'. Le forme corrette sono df.ffill() e df.bfill(). La firma nella documentazione pandas 3.0.5 non contiene più quel parametro: DataFrame.fillna(value, *, axis=None, inplace=False, limit=None) [1]. La firma e il testo del messaggio sono stati verificati eseguendoli su pandas 3.0.5, non leggendo la documentazione. [STATO AL: 2026-08-22]Più strumenti contemporaneamente. Per l'analisi di portafogli valutari, pandas offre un indice gerarchico (MultiIndex): un DataFrame contiene dati di più coppie, indicizzati per coppia e tempo. Permette correlazioni su un asse temporale comune, purché tutte le serie siano prima portate nello stesso fuso e sulla stessa griglia di timestamp. Altrimenti si calcola la correlazione fra dati sfalsati.
Salvare i risultati. df.to_csv() è leggibile e universale. df.to_parquet() produce file più piccoli e lettura più rapida, preservando tipi delle colonne e fuso orario dell'indice [1] : CSV scrive tutto come testo e richiede di ripetere l'interpretazione al caricamento. Parquet è più comodo per dati di lavoro voluminosi, ma richiede un motore di scrittura aggiuntivo: inserirlo in requirements.txt.
4. Esempio minimo: CSV → UTC → rendimenti → verifica delle lacune → salvataggio
Il primo script non dovrebbe inviare ordini e neppure collegarsi a un broker. Dovrebbe fare qualcosa di utile, verificabile e completamente sicuro: caricare un file, sistemare i tempi, calcolare i rendimenti, dichiarare chiaramente i problemi dei dati e salvare il risultato.
Ipotesi. Un file CSV con le colonne time,open,high,low,close,tick_volume. La colonna time in formato ISO 8601 con indicatore di fuso (Z significa UTC). L'intervallo è dichiarato orario. Il file del test contiene deliberatamente tre difetti presenti nelle esportazioni reali: un timestamp duplicato, un'ora mancante e una cella del volume vuota. Le sei righe sono inserite direttamente nel codice per riprodurre esattamente il risultato senza scaricare file: un risultato non riproducibile non è un risultato. Ambiente: Python 3.12.10, pandas 3.0.5, numpy 2.5.3, Windows 11, [STATO AL: 2026-10-08].
"""Esempio minimo: CSV -> UTC -> rendimento logaritmico -> lacune -> salvare."""
import io
from pathlib import Path
import numpy as np
import pandas as pd
# Dati nel codice per riprodurre il risultato senza scaricare file.
# Sei righe con tre difetti deliberati: timestamp 01:00 duplicato,
# ora 03:00 mancante e cella del volume vuota alle 02:00.
CSV_DATA = """time,open,high,low,close,tick_volume
2026-06-01T00:00:00Z,1.08140,1.08170,1.08131,1.08153,812
2026-06-01T01:00:00Z,1.08153,1.08190,1.08150,1.08176,640
2026-06-01T01:00:00Z,1.08153,1.08195,1.08149,1.08182,655
2026-06-01T02:00:00Z,1.08182,1.08188,1.08130,1.08141,
2026-06-01T04:00:00Z,1.08120,1.08125,1.08040,1.08050,903
2026-06-01T05:00:00Z,1.08050,1.08061,1.07980,1.07993,774
"""
OUTPUT = Path("EURUSD_H1_returns.csv")
INTERVAL = "1h" # frequenza dichiarata dei dati di input
def load_data(source) -> pd.DataFrame:
"""Carica CSV e imposta un indice con fuso orario UTC.
Non ordina deliberatamente: l'ordine delle righe informa di per sé
sulla qualità dell'esportazione. Si ordina dopo il report.
"""
df = pd.read_csv(source)
# utc=True impone un fuso; altrimenti pandas può restituire un indice privo di fuso
df["time"] = pd.to_datetime(df["time"], utc=True, format="ISO8601")
return df.set_index("time")
def quality_report(df: pd.DataFrame, interval: str) -> dict:
"""Contatori da vedere prima di calcolare qualsiasi cosa."""
expected = pd.date_range(df.index.min(), df.index.max(), freq=interval, tz="UTC")
return {
"rows": int(len(df)),
"index_duplicates": int(df.index.duplicated().sum()),
"missing_cells": int(df.isna().sum().sum()),
"missing_timestamps": int(len(expected.difference(df.index))),
# Misurato PRIMA di ordinare: dopo sort_index() il controllo sarebbe sempre
# vero e non rileverebbe un file con cronologia alterata.
"file_chronology_ok": bool(df.index.is_monotonic_increasing),
}
def add_returns(df: pd.DataFrame) -> pd.DataFrame:
"""Rendimento logaritmico = ln(close_t / close_(t-1)). Unità: logaritmo naturale, adimensionale."""
df = df[~df.index.duplicated(keep="last")].copy()
df["log_return"] = np.log(df["close"]).diff()
return df
def main() -> None:
df = load_data(io.StringIO(CSV_DATA)) # in pratica: load_data(Path("EURUSD_H1.csv"))
print("controllo qualità:", quality_report(df, INTERVAL))
df = add_returns(df.sort_index())
print("candele dopo deduplicazione:", len(df))
print("somma dei rendimenti logaritmici:", round(float(df["log_return"].sum()), 6))
print("deviazione standard logaritmica (intervalli 1h e 2h):",
round(float(df["log_return"].std()), 6))
df.to_csv(OUTPUT)
print("salvato:", OUTPUT)
if __name__ == "__main__":
main()
Output dell'esecuzione sui dati inseriti nel codice e nell'ambiente descritto: riproducibile esattamente:
controllo qualità: {'rows': 6, 'index_duplicates': 1, 'missing_cells': 1, 'missing_timestamps': 1, 'file_chronology_ok': True}
candele dopo deduplicazione: 5
somma dei rendimenti logaritmici: -0.00148
deviazione standard logaritmica (intervalli 1h e 2h): 0.000467
salvato: EURUSD_H1_returns.csv
Interpretazione. Lo script ha rilevato i tre difetti inseriti prima di calcolare qualunque cosa: è questo il suo valore. La somma dei rendimenti logaritmici è −0,00148; con cinque candele dopo la deduplicazione, cioè quattro intervalli fra prezzi osservati consecutivi, significa un calo complessivo del cambio di circa lo 0,15 per cento nella finestra studiata. Sei è il numero di righe del file, non di candele: una era duplicata. La deviazione standard di 0,000467 è una grandezza per singola candela H1, non annuale né giornaliera: confrontarla senza dichiarare l'intervallo non ha senso. Il rendimento logaritmico è adimensionale e additivo nel tempo: per questo si somma invece di moltiplicarlo, a differenza dei rendimenti semplici.
Limiti. Cinque candele illustrano la meccanica, non sono un campione statistico: da così poche osservazioni non segue nulla sul mercato. Lo script non modella spread, commissioni o slippage, perché non è un backtest: calcola rendimenti dei prezzi, non il risultato del conto. Rilevare timestamp mancanti presuppone che i dati abbiano davvero la frequenza oraria dichiarata. Su dati che comprendono un fine settimana, molti timestamp «mancanti» sono del tutto normali: la soglia d'allarme va quindi fissata considerando il calendario di negoziazione. Un'altra trappola riguarda la misura: lo script rileva la lacuna ma non la riempie, quindi il rendimento calcolato sull'ora mancante copre due intervalli, non uno. Qui riguarda uno dei quattro rendimenti, eppure l'insieme è etichettato «per candela H1»: una semplificazione, non una misura precisa. Due le strade corrette: reindicizzare su una griglia oraria completa lasciando vuota la lacuna oppure dichiarare esplicitamente il mescolamento degli orizzonti come limite della misura.
Perché la cronologia viene verificata prima di ordinare. Se load_data() terminasse con una chiamata a sort_index(), il campo file_chronology_ok sarebbe sempre vero: il controllo annunciato nel report non potrebbe rilevare nulla e un file con ordine alterato passerebbe senza traccia. Un controllo che non può fallire è peggiore di nessun controllo, perché dà una falsa sensazione di validazione. Perciò l'ordine viene misurato sui dati appena caricati e si ordina solo dopo il report.
Stato della verifica: questo frammento è stato eseguito nell'ambiente dichiarato e l'output sopra è quello effettivo del programma. In altre versioni di pandas il formato del dizionario o l'arrotondamento possono apparire diversi.
5. Visualizzazione: una domanda per grafico
Un numero riassuntivo nasconde l'essenziale: quando la strategia perdeva, per quanto tempo e se l'intero risultato dipende da tre operazioni. Un grafico non decora il report: pone ai dati una domanda a cui la tabella non risponde.
matplotlib è la libreria grafica di base in Python: statica e dalla sintassi piuttosto scomoda. plotly offre grafici interattivi: passare su un punto per data e valore, ingrandire un frammento, esportare in HTML. Con dati molto grandi può essere più lenta e richiede un'installazione separata. Per esplorare in un notebook è più comoda plotly; per i grafici salvati nei report, matplotlib.
Quattro grafici che conviene saper tracciare, perché rispondono a domande diverse:
- Curva del patrimonio: risultato cumulato nel tempo. Risponde a «se e quando è cresciuto», non a «quanto guadagnerò»
- Grafico del drawdown: distanza dal massimo progressivo in ogni momento. Mostra il drawdown più profondo e il tempo di recupero. Dichiarare sempre se è calcolato su saldo o patrimonio corrente e se in percentuale o valuta
- Mappa di calore delle sessioni: risultato per ora e giorno della settimana. Richiede di chiarire prima il fuso orario, altrimenti descrive ore inesistenti
- Distribuzione dei rendimenti: istogramma dei singoli risultati. Mostra le code pesanti e l'asimmetria invisibili in un'unica metrica di rischio
Per grafici a candele direttamente da un DataFrame esiste mplfinance, uno strato sopra matplotlib, con una riserva derivata dalla sezione 2: l'ultima pubblicazione risale ad agosto 2023 e conserva un numero di pre-release (0.12.10b0) [13] [STATO AL: 2026-10-08]. Non la esclude a priori, ma prima di aggiungerla a requirements.txt, verificare sui propri dati che funzioni con le proprie versioni di matplotlib e pandas: una nuova pubblicazione non lo farà al proprio posto. Per trasformare uno script in una semplice dashboard con cursori e menu a discesa esiste streamlit, un progetto con pubblicazioni regolari [13], avviato con streamlit run dashboard.py. Entrambe le librerie fanno risparmiare tempo e aggiungono dipendenze a requirements.txt: aggiungerle consapevolmente.
Regola pratica: se non si riesce a dire in una frase a quale domanda risponda un grafico, non serve. Un grafico senza domanda è decorazione, che nell'analisi dei risultati può costare cara: suggerisce conclusioni non contenute nei dati.
6. Backtesting in Python: cosa appartiene al codice e cosa alla metodologia
Qui passa un confine facile da ignorare. Python è responsabile dell'implementazione del test: caricamento dei dati, calcolo del segnale, attribuzione del risultato al periodo corretto, addebito dei costi e calcolo delle metriche. Non è responsabile della metodologia: divisione del campione, numero di varianti, modello di esecuzione e valutazione della robustezza. La metodologia è trattata in un articolo separato: Conti demo, backtesting e forward test; nessuna libreria migliore può sostituirla.
pandas da solo non è un backtest. Moltiplicare una colonna di segnali per una di rendimenti produce una serie di risultati, ma non modella nulla fra decisione ed esecuzione: momento e prezzo d'ingresso, spread, commissione, swap, esecuzioni parziali, rifiuti. Può essere una semplificazione accettabile nella prima selezione delle idee, purché la si chiami semplificazione, non risultato.
Oltre a un proprio ciclo, nell'ecosistema Python esistono tre approcci agli strumenti pronti. La tabella confronta il funzionamento, non promesse di prestazioni: i tempi dipendono da dati, hardware e versione; se servono, misurarli e registrare l'ambiente.
| Caratteristica | Approccio completo a eventi | Approccio vettorializzato | Approccio minimo a eventi |
|---|---|---|---|
| Libreria di esempio | backtrader | vectorbt | backtesting.py |
| Come calcola | Candela per candela, con modello proprio di ordini e portafoglio | Operazioni su interi array contemporaneamente | Candela per candela, modello semplificato |
| Quantità di codice per una strategia semplice | Molto (classi, ereditarietà) | Poco, se si pensa per colonne | Poco |
| Uso naturale | Logica di portafoglio, molti strumenti, ordini complessi | Esame massivo di varianti e filtro iniziale | Apprendimento e verifica rapida del senso di un'idea |
| Costo principale | Curva di apprendimento ripida | Debug più difficile; facile un errore di spostamento silenzioso | Limiti con strategie complesse |
| Cosa verificare prima dell'uso | Data dell'ultima pubblicazione PyPI, compatibilità con le proprie versioni pandas e NumPy, modellazione dei costi e del momento di esecuzione | ||
backtrader ha alle spalle pause pluriennali fra pubblicazioni e l'ultima risale ad aprile 2023; esistono anche fork della comunità mantenuti in modo diverso; vectorbt esiste come progetto aperto e come versione commerciale sviluppata separatamente (pubblicazione aperta di luglio 2026): chiarire prima a quale si riferisca la documentazione letta; backtesting.py ha avuto una pausa pluriennale nelle pubblicazioni, ma l'ha interrotta con una nuova versione a luglio 2026: il passato qui conta. Le tre date provengono dal registro dei pacchetti [13]. Nulla di ciò esclude una libreria a priori: la esclude l'incompatibilità con le versioni usate. [STATO AL: 2026-08-22]L'ultima riga non è una formalità. Ogni framework assume un prezzo e un momento di esecuzione: spesso è quell'ipotesi, non la logica della strategia, a decidere il risultato. Prima di fidarsi di una libreria, cercare nella documentazione tre risposte: a quale prezzo esegue l'ingresso, come addebita i costi e cosa fa con un gap fra candele.
La libertà del codice è anche un rischio. Un tester integrato impone regole e protegge da alcuni errori. Il proprio codice non protegge da nulla: non chiede se addestramento e validazione siano separati, non nota un segnale basato su un prezzo allora inesistente e non conta le varianti provate. Tutta la responsabilità passa all'autore: ecco perché le sezioni successive trattano ciò che si può compromettere silenziosamente nel codice.
7. API dei broker: autenticazione, limiti, errori, ambiente di test
«Il broker offre un'API» viene talvolta letto come «lo script ha accesso illimitato al conto». Non funziona così: un'API è un insieme di endpoint con autenticazione, permessi, limiti alle richieste e codici di errore definiti. A volte, non sempre, è divisa in test e produzione; la modalità della divisione va cercata nella documentazione, non presunta. Prima di scrivere una riga occorre rispondere a nove domande: come avviene l'autenticazione, quali permessi abbia il token, quante richieste al secondo siano ammesse, come si distinguano test e produzione, quali tipi di dati ritorni, quali errori definisca, se esistano restrizioni regionali, quale sia la politica di mantenimento della connessione e se il token possa inserire ordini.
I limiti sono concreti e pubblicati, ma occorre leggere di quale limite si tratti. La distinzione si perde nella maggior parte delle guide e decide come progettare il ciclo. In OANDA v20 due numeri in due documenti significano cose diverse. La guida per sviluppatori indica il valore imposto: «120 richieste al secondo. Quelle eccedenti riceveranno l'errore HTTP 429» per l'API REST, insieme a venti flussi attivi e due nuove connessioni al secondo, tutti conteggiati per IP del richiedente [15]. Il documento delle buone pratiche dà invece una raccomandazione: il fornitore consiglia di limitarsi a cento richieste al secondo su una connessione esistente e a due nuove connessioni al secondo [5]. [STATO AL: 2026-08-22]
Cento al secondo è quindi la soglia consigliata; oltre centoventi il server risponde con errore 429. Supporre «100/s è il massimo» non danneggia, ma prenderlo per un limite rigido senza gestire 429 farà fallire il codice al primo superamento: limite imposto e raccomandazione non coincidono. Sono numeri di un fornitore, non una regola di mercato: presso un altro broker saranno diversi e [FUNZIONE DIPENDENTE DA BROKER/CONTO]. Verificarli nella documentazione del proprio fornitore prima di scrivere un ciclo di interrogazione.
L'ambiente di test è un host diverso, non una modalità diversa. In OANDA la separazione si vede dall'indirizzo: api-fxpractice.oanda.com per l'ambiente di prova e api-fxtrade.oanda.com per la produzione, con una coppia analoga per il flusso dati [15] [STATO AL: 2026-08-22]. È positivo perché l'errore può essere intercettato nel codice: il server di produzione può essere protetto da un'altra condizione. Peggio se il fornitore usa un solo indirizzo e distingue gli ambienti solo dal token: l'unica barriera è la configurazione.
Quattro percorsi tipici per collegare Python a un conto: architetture diverse, non livelli di «sofisticazione»:
| Soluzione | Percorso effettivo della connessione | Condizioni | Note |
|---|---|---|---|
| OANDA v20 (REST + streaming) | Script → server del broker, direttamente via HTTP | Conto presso quel broker; host separati per prova e produzione | Documentazione pubblica, limiti espliciti. Non è uno standard del settore, ma l'API di un fornitore |
| Interactive Brokers (TWS / IB Gateway) | Script → applicazione intermediaria locale → server del broker | Richiede TWS o IB Gateway in esecuzione; modello asincrono | Chiudere l'applicazione intermediaria interrompe la connessione dello script. Le librerie wrapper semplificano la gestione ma non eliminano la dipendenza. Attenzione al nome: la spesso citata ib_insync non ha nuove pubblicazioni da luglio 2023 e si basa su una vecchia versione dell'API TWS; una continuazione mantenuta attivamente è ib_async [13] [STATO AL: 2026-10-08] |
Il pacchetto ufficiale MetaTrader5 di integrazione | Script → terminale MT5 installato sullo stesso computer → server del broker | Il terminale MT5 deve essere installato sullo stesso computer; la chiamata initialize() lo avvia se non è in esecuzione e usa le credenziali salvate nel terminale o passate nella chiamata [18]; l'integrazione è disponibile dalla build 2085 (2019) [7] | È comunicazione con il terminale, non un'API indipendente del broker [6]. La dipendenza dal terminale è una proprietà, non un difetto. Il pacchetto ufficiale PyPI si installa solo su Windows; su macOS e Linux richiede terminale e Python sotto Wine oppure un livello intermediario separato. [FUNZIONE DIPENDENTE DA VERSIONE/AMBIENTE] |
| Ponte cloud esterno (es. MetaApi) | Script → servizio esterno → terminale nel cloud del fornitore → server del broker | Richiede accesso al conto a terzi; prezzi e limiti dipendono dal piano | Aggiunge un anello e un punto di fiducia. Un ponte che inserisce ordini richiede la password principale, non quella investitore: potere di disporre del conto, non sola lettura. Verificare i prezzi dal fornitore [FUNZIONE DIPENDENTE DAL PIANO] |
Al di fuori del mercato valutario è utile conoscere anche ccxt: una libreria che uniforma l'accesso alle borse di criptovalute. Non riguarda i conti FX/CFD presso broker al dettaglio, ma viene talvolta scambiata per una soluzione universale.
cTrader. Con cTrader, gli algoritmi si scrivono in cTrader Algo, che supporta nativamente C# e Python, compresi cBot, indicatori e plugin [8] [STATO AL: 2026-08-22]. È un modello diverso: il codice gira nella piattaforma, non come processo separato collegato a un'API. Verificare funzioni e avvio nella documentazione attuale del fornitore, perché il prodotto è in sviluppo attivo.
Chiavi e password: mai nel codice
Una chiave API scritta nello script finisce nel repository, nei backup e nella cronologia: cancellare la riga non la rimuove da Git. I repository pubblici vengono scansionati automaticamente per queste stringhe. I soli posti accettabili sono una variabile d'ambiente, un file di configurazione esterno al repository ed elencato in .gitignore oppure un gestore di segreti che fornisca il valore tramite uno di questi mezzi.
"""Configurazione e segreti: niente dati sensibili nel file di codice."""
import os
from dataclasses import dataclass
HOSTS = {
"practice": "https://api-fxpractice.oanda.com",
"live": "https://api-fxtrade.oanda.com",
}
class MissingConfiguration(RuntimeError):
"""Solleva se mancano dati senza cui non si deve avviare."""
@dataclass(frozen=True)
class Configuration:
token: str
account: str
environment: str
@property
def host(self) -> str:
return HOSTS[self.environment]
def _required(name: str) -> str:
value = os.environ.get(name, "").strip()
if not value:
# Nessun valore predefinito, volutamente: un token vuoto invierebbe
# senza autorizzazione anziché arrestare il programma.
raise MissingConfiguration(f"Variabile d'ambiente mancante {name}")
return value
def load_config() -> Configuration:
environment = os.environ.get("BROKER_ENV", "practice").strip().lower()
if environment not in HOSTS:
raise MissingConfiguration(f"BROKER_ENV deve essere uno di {sorted(HOSTS)}")
return Configuration(
token=_required("BROKER_API_TOKEN"),
account=_required("BROKER_ACCOUNT_ID"),
environment=environment,
)
Tre decisioni sono deliberate: l'ambiente predefinito è di prova, non produzione, così un errore di configurazione conduce alla demo. Una variabile mancante arresta il programma anziché sostituirla con una stringa vuota. E il token non ha mai un valore predefinito, neppure commentato: un segreto commentato è un segreto divulgato.
La libreria python-dotenv citata prima non sostituisce questo codice, lo precede. La chiamata load_dotenv() (dopo from dotenv import load_dotenv) carica un file .env esterno al repository nelle variabili d'ambiente del processo; solo dopo le legge load_config() La funzione di configurazione non sa e non deve sapere da dove provenga il valore: file, impostazioni di sistema o gestore di segreti. Il file .env va inserito in .gitignore insieme all'intera directory di configurazione.
Acquisizione dati: timeout, tentativi, codici di risposta
Una richiesta senza timeout può bloccarsi indefinitamente e fermare il processo. Tentativi illimitati possono esaurire la quota e provocare un blocco. Il frammento mostra le protezioni minime per leggere dati e una distinzione che poi salva il conto: si possono ripetere solo operazioni idempotenti, che ripetute non cambiano lo stato presso il broker.
import logging
import time
import requests
TIMEOUT_S = 10.0 # secondi di inattività connessione/lettura, non scadenza totale
MAX_ATTEMPTS = 4 # numero di tentativi incluso il primo
BACKOFF_S = 1.0 # base del ritardo esponenziale in secondi
log = logging.getLogger("data")
class ApiError(RuntimeError):
"""Errore non risolvibile riprovando."""
def fetch_candles(session: requests.Session, host: str, token: str,
instrument: str, parameters: dict) -> dict:
"""Ripete solo GET: non cambia lo stato del broker."""
url = f"{host}/v3/instruments/{instrument}/candles"
headers = {"Authorization": f"Bearer {token}"}
for attempt in range(1, MAX_ATTEMPTS + 1):
delay = BACKOFF_S * 2 ** (attempt - 1)
try:
response = session.get(url, headers=headers, params=parameters, timeout=TIMEOUT_S)
except requests.RequestException as error:
if attempt == MAX_ATTEMPTS:
raise ApiError(f"Nessuna connessione dopo {MAX_ATTEMPTS} tentativi") from error
log.warning("tentativo %s: %s", attempt, error)
time.sleep(delay)
continue
if response.status_code == 429:
# Limite superato. L'header Retry-After, se restituito dal broker,
# è più affidabile che indovinare.
try:
wait = float(response.headers.get("Retry-After", delay))
except (TypeError, ValueError):
# La specifica ammette anche una data HTTP invece dei secondi;
# senza questo ramo float() interromperebbe tutto il download.
wait = delay
log.warning("limite richieste, attesa %.1f s", wait)
time.sleep(wait)
continue
if 500 <= response.status_code < 600:
log.warning("errore del server %s, nuovo tentativo", response.status_code)
time.sleep(delay)
continue
if response.status_code != 200:
raise ApiError(f"HTTP {response.status_code}: {response.text[:200]}")
try:
data = response.json()
except ValueError as error:
raise ApiError("Risposta JSON non valida") from error
if not isinstance(data, dict):
raise ApiError("La risposta JSON deve essere un oggetto")
if not isinstance(data.get("candles"), list) or not data["candles"]:
raise ApiError("Nessuna candela nella risposta: verificare strumento e date")
return data
raise ApiError(f"Nessuna risposta valida dopo {MAX_ATTEMPTS} tentativi")
Stato della verifica: testato offline, senza collegamento a un broker. La funzione pubblicata fetch_candles() è stata eseguita con un mock in memoria della sessione HTTP, non un server reale. Nove scenari coprono 429 con Retry-After in secondi, seguito nello stesso scenario da 503 e successo, una lista di candele vuota, 404, un header con data HTTP, tentativi esauriti, un'eccezione di connessione, JSON non valido, risposta non oggetto e tipo non valido di candles. Le chiamate a sleep() sono state sostituite dalla registrazione del ritardo richiesto; il test non apre socket né misura tempi di rete. Ambiente: Python 3.12.10, requests 2.34.2, Windows 11, verificato il 2026-10-08. Il report effettivo degli scenari:
A: 429 secondi -> 503 -> 200: chiamate=3; attese=[3.0, 2.0]; una candela
B: lista candele vuota: chiamate=1; attese=[]; errore atteso
C: HTTP 404: chiamate=1; attese=[]; errore atteso
D: data HTTP, alternativa: chiamate=2; attese=[1.0]; una candela
E: limite tentativi: chiamate=4; attese=[0.0, 0.0, 0.0, 0.0]; errore atteso
F: errore di trasporto: chiamate=4; attese=[1.0, 2.0, 4.0]; errore atteso
G: JSON non valido: chiamate=1; attese=[]; errore atteso
H: JSON non oggetto: chiamate=1; attese=[]; errore atteso
I: tipo errato di candles: chiamate=1; attese=[]; errore atteso
Cosa conferma questo risultato e cosa no. Conferma il comportamento previsto in nove scenari preparati. A verifica che 429 scelga i tre secondi del server, non il secondo del codice; D verifica l'alternativa quando float() non può interpretare una data HTTP. F verifica che timeout=10 sia passato, si facciano quattro tentativi e un'eccezione termini i guasti di trasporto simulati. Non stabilisce una scadenza per una connessione reale: il timeout di requests non è una scadenza totale e l'attesa è simulata. B, C e G–I verificano il rifiuto di risposte vuote o non valide. Non verifica disponibilità API, ogni campo dati o la rete di uno specifico broker.
Non conferma nulla sul broker stesso. Percorso dell'endpoint, nomi dei parametri, forma della risposta e restituzione di Retry-After sono proprietà di una specifica API [FUNZIONE DIPENDENTE DA BROKER/CONTO] e vanno verificate nella documentazione e testate su un conto di prova. Il mock dimostra la correttezza del codice, non delle ipotesi sul server altrui: la distinzione è l'intero valore del test.
La latenza va misurata, non citata. Gli intervalli «50–200 ms per questa API, 100–500 ms per quell'altra» non valgono senza posizione, collegamento, ora e metodo. Distinguere quattro misure: risposta dell'endpoint dati, tempo dall'invio dell'ordine alla conferma di accettazione, tempo alla conferma di esecuzione ed età del dato alla decisione. Sono grandezze diverse e solo una è chiamata comunemente «latenza». Misurarle nella propria catena, registrarle e citare data, broker e numero di tentativi. Un ping ICMP non è nessuna delle quattro.
Interrogazioni o flusso. Con REST periodico, interrogare ogni cinque secondi significa poter decidere su un prezzo di cinque secondi prima; aumentare la frequenza consuma quota. Un flusso, WebSocket o HTTP secondo l'API, inverte il modello: il server invia gli aggiornamenti. Serve per reazioni intrabarra o per non esaurire la quota. Per l'apertura della successiva H1 bastano interrogazioni, più semplici da mantenere. Un flusso aggiunge tre problemi. Primo, la lacuna dopo la riconnessione: dopo trenta secondi di interruzione, il socket non riproduce quel periodo; serve una richiesta storica separata prima di dire completa la serie. Secondo, il rilevamento di una connessione inattiva: un socket senza dati appare come un mercato fermo; servono un segnale di presenza (heartbeat o keep-alive) e un contatore di silenzio per dichiarare l'interruzione e ristabilire la connessione. Terzo, ordine e duplicati: dopo la ripresa può arrivare un messaggio già elaborato. Come per gli ordini, si risolve con un identificativo dell'evento e verificando se sia già registrato.
Limiti separati per dati e ordini. Un'API seria separa prezzi e transazioni; i limiti alle modifiche del conto possono essere molto inferiori alla lettura. Tentare una dozzina di modifiche allo stop al secondo può provocare il blocco proprio sul percorso transazionale, lasciando una posizione aperta temporaneamente senza accesso programmatico. Occorre quindi limitare le modifiche nel proprio codice, non contare sulla tolleranza del broker.
8. Dal notebook al servizio: differenze fra codice di esecuzione e script
È il vero confine dell'articolo. Un notebook statistico può arrestarsi a metà: lo si riesegue. Il codice autorizzato sul conto lascia invece uno stato: posizione aperta, ordine parzialmente modificato, doppio ingresso. La differenza non è la qualità del codice, ma le conseguenze irreversibili.
Tre livelli di automazione: diversi per chi prende la decisione finale, non per tecnologia:
Livello 1: notifiche. Lo script controlla condizioni e invia un messaggio. Non accede al conto né richiede un token per ordini; decide ed esegue una persona. Solo qui un errore costa soltanto irritazione.
Livello 2: preparazione con conferma. Lo script prepara strumento, direzione e dimensione e attende conferma esplicita dell'operatore prima di inviare. Intercetta errori aritmetici prima della posizione e mantiene l'umano nella decisione. Per un trader al dettaglio che non voglia mantenere software è di solito il livello finale, non transitorio: giudizio editoriale, non risultato di ricerca.
Livello 3: funzionamento senza conferma. Lo script apre e chiude da solo. Richiede tutti gli elementi precedenti, test prima in prova e poi con dimensione minima, e supervisione iniziale. Quante operazioni bastino riguarda campione e potenza statistica, non numeri tondi; ne tratta l'articolo sui test.
Livello di esecuzione con barriere di sicurezza
Se si mostra codice per ordini, occorre mostrare cosa lo arresta. Il modulo è interamente eseguibile e non chiama la rete: costruisce un ordine, verifica limiti e si ferma alle barriere. L'invio è un frammento separato, volutamente non codice.
"""Livello di esecuzione: validazione, modo test e barriera esplicita prima del reale."""
import logging
import os
import sys
import uuid
ALLOWED_INSTRUMENTS = {"EUR_USD"}
MAX_UNITS = 1_000 # unità della valuta base, non lotti: dipende dall'API
# La conferma reale non è in questo file e non può essere letta da qui.
# Il programma la legge da una variabile d'ambiente e accetta il
# numero del conto scelto consapevolmente, digitato a mano nella shell
# prima dell'avvio. La portata è descritta nel riquadro sotto l'output.
CONFIRMATION_VARIABLE = "BROKER_LIVE_CONFIRMATION"
# Senza configurazione non ci sono voci log.info: il livello predefinito
# è WARNING e manca una destinazione. Il file lascia traccia dopo guasti,
# il flusso mostra lo stesso dal vivo. Nei programmi più grandi configurare
# spetta al punto d'ingresso, non a un modulo.
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s %(message)s",
handlers=[logging.FileHandler("execution.log", encoding="utf-8"),
logging.StreamHandler(sys.stdout)],
)
log = logging.getLogger("execution")
class SafetyGateError(RuntimeError):
"""Arresta l'ordine prima che esca dal processo."""
def new_order_id() -> str:
"""Chiamato UNA VOLTA per intenzione d'ordine, salvato prima dell'invio.
Lo stesso valore deve tornare a ogni tentativo dello stesso ordine;
altrimenti non si può riconoscere un duplicato.
"""
return str(uuid.uuid4())
def build_order(instrument: str, units: int, order_id: str) -> dict:
if instrument not in ALLOWED_INSTRUMENTS:
raise SafetyGateError(f"Strumento non ammesso: {instrument}")
# bool è sottoclasse di int, quindi isinstance(True, int) è vero; senza rifiuto
# esplicito, un booleano passerebbe la validazione e finirebbe nel payload.
if isinstance(units, bool) or not isinstance(units, int) or units == 0:
raise SafetyGateError("La dimensione deve essere un intero non nullo")
if abs(units) > MAX_UNITS:
raise SafetyGateError(
f"Dimensione {abs(units)} supera il limite {MAX_UNITS}")
return {
"order": {
"type": "MARKET",
"instrument": instrument,
"units": str(units),
# L'identificativo viene dall'esterno e non cambia fra tentativi.
# Creandolo qui, ogni tentativo porterebbe un altro valore.
# Nome e vincoli del campo appartengono alla specifica API.
"clientExtensions": {"id": order_id},
}
}
def _operator_confirmation() -> str:
"""Legge un valore esterno; il codice non lo conosce né lo conserva."""
return os.environ.get(CONFIRMATION_VARIABLE, "").strip()
def submit_order(session, cfg, instrument: str, units: int, order_id: str,
test_mode: bool = True) -> dict | None:
"""Non invia per impostazione predefinita. Produzione richiede due decisioni."""
order = build_order(instrument, units, order_id)
if test_mode:
log.info("MODO TEST [%s] ordine creato, nulla inviato: %s",
cfg.environment, order)
return None
if cfg.environment == "live" and _operator_confirmation() != cfg.account:
raise SafetyGateError(
"Produzione richiede conferma esplicita dell'operatore")
# Barriere superate: qui termina il modulo. Non invia nulla,
# perché non è possibile eseguirlo onestamente; la struttura segue nello schema.
raise NotImplementedError(
"Invio non implementato qui: si veda lo schema")
L'identificativo nasce una volta, non a ogni tentativo. Per riconoscere duplicati dopo una disconnessione, il valore va calcolato all'intenzione di inserire l'ordine, salvato durevolmente prima dell'invio e riutilizzato per ogni tentativo dello stesso ordine. Se uuid.uuid4() fosse nella costruzione dell'ordine, ogni tentativo avrebbe un valore diverso e non sarebbe riconoscibile come ripetizione. Perciò build_order() riceve l'identificativo come argomento anziché generarlo.
Cosa non risolve l'identificativo. Il campo clientExtensions nel payload è la soluzione di un fornitore, non standard né garanzia documentata di idempotenza. OANDA lo descrive come dati personalizzati facoltativi e avverte: «Non impostare, modificare o eliminare tradeClientExtensions se il conto è associato a MT4» [14] (verificato il 2026-08-22). Non promette di rifiutare come duplicato un ordine a mercato con lo stesso identificativo; l'esecuzione è immediata e la finestra di riconoscimento minima. Servono due misure proprie: registro durevole dell'intenzione prima dell'invio e riconciliazione con ordini e transazioni richiesti al broker dopo un guasto. Verificare nome e vincoli nella propria API: altrove cambia o manca.
Inviare un ordine: schema strutturale
Non segue una funzione da copiare, ma uno schema: passi e operazioni trasferibili nel proprio codice, non da incollare ed eseguire. La scelta è deliberata. La redazione non ha eseguito questo ramo né intende farlo: inviare ordini richiede un conto; provare transazioni su quello altrui significa rischio altrui. Una funzione completa sarebbe un artefatto mai eseguito e insegnerebbe fiducia nel codice non verificato. La stessa scelta riguarda gli schemi MQL nell'art. 11.5 e per quello Pine Script nell'art. 11.2.
SCHEMA STRUTTURALE - inviare un ordine e gestire una risposta mancante
Orientamento per il livello HTTP. Non è codice completo ed eseguibile.
dichiarazione: StanNieznany - risposta assente, non si sa se esista l'ordine.
Tipo separato, non sottoclasse di errore di rete: un errore
di rete è ripetibile, questo stato non va mai ripetuto.
input: ordine (costruito e validato), order_id, configurazione, sessione HTTP
output: risposta del broker o StanNieznany; mai successo silenzioso
1. REGISTRO DUREVOLE - prima di tutto
scrivere fuori dalla memoria del processo: order_id, timestamp UTC,
instrument, units, environment, status := INTENT
forzare la scrittura su disco prima di proseguire
altrimenti il riavvio perde l'intenzione e il programma
non sa più cosa abbia già tentato
2. DIARIO - anch'esso prima dell'invio
registrare: order_id + environment + contenuto dell'ordine
il token non va mai nel diario
mascherare il conto salvo gli ultimi quattro caratteri
il diario è letto dopo da una persona; il registro del passo 1 dal programma
3. UNA RICHIESTA, ZERO TENTATIVI AGGIUNTIVI
POST al percorso ordini indicato dalla propria documentazione API
header di autorizzazione con token dalla configurazione, mai dal codice
timeout obbligatorio; senza, la chiamata può bloccarsi per sempre
4. Risposta non arrivata (timeout o errore di trasporto)
NON ripetere; POST non è idempotente
status nel registro := UNKNOWN
sollevare StanNieznany
RICONCILIAZIONE: chiedere al broker ordini e transazioni
degli ultimi minuti, cercare order_id e solo dopo aver
stabilito se l'ordine esista prendere una nuova decisione
(lo schema non contiene la procedura; si veda il paragrafo sotto)
5. Risposta arrivata
registrare: codice HTTP + corpo troncato
codice fuori da (200, 201) -> status := REJECTED -> sollevare errore
corpo non JSON -> status := UNKNOWN -> sollevare StanNieznany
altrimenti -> status := ACCEPTED -> restituire risposta
Cosa non contiene e non può contenere:
nessuna funzione da incollare ed eseguire su un conto,
né il nome del campo protettivo: deriva dalla documentazione
di una specifica API, non da un esempio altrui.
Cosa non fa questo schema, importante quanto ciò che descrive. Non aggiunge stop loss né take profit. Non è una svista: ogni API li esprime diversamente, con campi, distanza o prezzo assoluto e validità propria. Il frammento viene dalla documentazione del proprio broker, non da esempi altrui. La conseguenza pratica riguarda il passo 4: dopo uno StanNieznany può esserci una posizione aperta senza protezione e senza certezza che esista. Nel codice autorizzato all'invio, il livello protettivo viaggia nella stessa richiesta dell'ingresso; altrimenti si apre una finestra senza stop.
Non mancano solo i livelli protettivi. Della lista «Notebook di ricerca ≠ programma di esecuzione», né modulo né schema hanno ancora monitoraggio, interruttore d'emergenza o procedura di distribuzione. Lo stato persistente compare nel passo 1 come requisito, non implementazione. La sezione su registri, stato e test ne parla, senza mostrarli in codice. La conclusione resta: materiale per apprendere l'architettura, non un programma di esecuzione.
Le barriere sono state testate offline: senza rete, broker o segreti, con configurazione simulata. Segue l'intero test, non solo l'output; altrimenti bisognerebbe crederci sulla parola, proprio la fiducia da cui mette in guardia l'articolo.
"""Test barriere senza rete: configurazione mock, nessun segreto o POST."""
from dataclasses import dataclass
@dataclass(frozen=True)
class MockConfiguration:
environment: str = "practice"
host: str = "https://api-fxpractice.oanda.com"
token: str = "dummy-token-for-offline-test"
account: str = "000-000-0000000-000"
def test_gates() -> None:
cfg = MockConfiguration()
order_id = new_order_id()
print("configurazione:", cfg.environment, cfg.host, "token_length=", len(cfg.token))
# session=None è deliberato: se tentasse un POST,
# il test fallirebbe con AttributeError anziché passare silenziosamente.
result = submit_order(None, cfg, "EUR_USD", 100, order_id)
assert result is None
print("1. modo test: nulla inviato - OK")
for description, instrument, units in [
("strumento non ammesso", "GBP_JPY", 100),
("dimensione oltre il limite", "EUR_USD", 5000),
("dimensione zero", "EUR_USD", 0),
("booleano invece di numero", "EUR_USD", True),
]:
try:
build_order(instrument, units, order_id)
except SafetyGateError as error:
print(f"2. barriera [{description}]:", error)
else:
raise AssertionError(f"Barriera attesa non attivata: {description}")
cfg_live = MockConfiguration(environment="live", host="https://api-fxtrade.oanda.com")
try:
submit_order(None, cfg_live, "EUR_USD", 100, order_id, test_mode=False)
except SafetyGateError as error:
print("3. barriera [reale senza conferma]:", error)
else:
raise AssertionError("Modo reale passato senza conferma")
print("tutte le barriere superate")
if __name__ == "__main__":
test_gates()
Output con Python 3.12.10 come nella sezione 2, senza rete né pacchetti esterni: tolto l'invio, il modulo non richiede più requests. La seconda riga è una voce di registro: lo stesso contenuto va insieme nel file execution.log e sullo standard output. Timestamp e identificativo cambiano a ogni esecuzione; il resto è ripetibile carattere per carattere.
configurazione: practice https://api-fxpractice.oanda.com token_length= 28
1. modo test: nulla inviato - OK
2. barriera [strumento non ammesso]: Strumento non ammesso: GBP_JPY
2. barriera [dimensione oltre il limite]: Dimensione 5000 supera il limite 1000
2. barriera [dimensione zero]: La dimensione deve essere un intero non nullo
2. barriera [booleano invece di numero]: La dimensione deve essere un intero non nullo
3. barriera [reale senza conferma]: Produzione richiede conferma esplicita dell'operatore
tutte le barriere superate
Cosa conferma questo output e cosa no. Cinque casi negativi hanno attivato le barriere: strumento non ammesso, dimensione eccessiva, zero, booleano e modo reale senza conferma. Le asserzioni verificano anche il modo test; sono stati controllati inoltre limiti ±1000, superamenti e tipi non validi. Il primo argomento è None, e l'invio non è implementato: il test non può inserire ordini. Non verifica l'API, la resilienza del registro o la mascheratura di ogni campo sensibile. Il token fittizio non è stampato, solo la lunghezza. Verificare a parte scrittura e recupero dei registri, regole di oscuramento e guasti del disco.
Non conferma né può confermare il comportamento su un'API reale: il ramo d'invio non esiste. Lo ha sostituito uno schema, eliminando l'unico punto di invio. È la differenza fra «non l'abbiamo eseguito» e «non c'è nulla da eseguire»: il primo lascia codice da incollare, il secondo una struttura da implementare leggendo la documentazione del proprio broker.
Il test deve rilevare anche una barriera mancante. Perciò verifica con un'asserzione il risultato del modo test e solleva AssertionError dopo ogni caso negativo se manca l'eccezione attesa. Il messaggio finale appare solo dopo il superamento. Resta un piccolo esempio: non copre formati del broker, ogni tipo o recupero dello stato. Nei progetti più grandi trasferire i casi in pytest, aggiungere casi positivi ed eseguirli a ogni modifica del validatore.
BROKER_LIVE_CONFIRMATION e confrontato con il conto: l'operatore deve digitarne consapevolmente il numero nella shell. Protegge da tre errori: «provare» produzione, confondere il conto, disattivare accidentalmente il test. Non protegge oltre: codice nello stesso processo può impostare la variabile e superare la barriera. Una conferma letta dal proprio ambiente non protegge da un programma. I veri limiti ai permessi sono esterni al codice: token senza diritto agli ordini, dove distinto dal broker, e limiti di posizione sul conto. [FUNZIONE DIPENDENTE DA BROKER/CONTO].Le unità non sono lotti. Il commento accanto a MAX_UNITS non è decorazione: le API esprimono dimensioni in unità base, lotti o contratti. Un errore non produce errori di sintassi né eccezioni, ma esposizione di due o tre ordini di grandezza maggiore con codice corretto. Verificare la convenzione nella documentazione e confermarla con un'operazione minima di prova. Due limiti del validatore: una dimensione calcolata in pandas o NumPy ha tipo numpy.int64, non sottoclasse del tipo incorporato int. Il validatore rifiuta un valore intero con un messaggio sugli interi: sicuro, ma confuso. Passare i numeri letti dal DataFrame per int(...) prima che arrivino a build_order(). Il secondo limite: order_id non viene verificato e un'annotazione non impone nulla. Poiché il riconoscimento dei duplicati dipende dall'identificativo, verificarne lunghezza e formato nell'API e imporli come il limite di dimensione.
Registri, stato e test
Un registro invece di print(). Il modulo logging offre livelli, timestamp e scrittura su file, ma non si attivano da soli. Senza configurazione il livello predefinito è WARNING e manca una destinazione, quindi le chiamate log.info(...) vengono scartate silenziosamente: sembra registrare ma non salva nulla. Per questo dopo un guasto il registro è spesso vuoto. Configurare una volta all'ingresso del programma, non nel modulo importato. La chiamata logging.basicConfig(...) è nel modulo solo perché lì è l'intero programma; il commento dice esplicitamente che nei progetti più grandi è il posto sbagliato. La configurazione all'importazione prende il controllo del registro del processo e una successiva logging.basicConfig(...) all'ingresso reale non fa nulla una volta che il logger principale ha destinazioni (salvo con force=True) senza errore [4]. Le impostazioni spariscono e le voci vanno dove ha deciso il modulo: lo stesso difetto silenzioso dall'altro lato. Registrare decisione e motivi, richiesta e risposta. Troncare la risposta per non riempire con errori HTML e alzare il limite in diagnosi. Mai registrare il token. Senza risposta non si distingueranno logica, rete e server.
execution.log, e nel programma completo anche la risposta: conto, identificativo, dimensione e prezzi. Il token non c'è né deve esserci: dieci test hanno prodotto undici voci e zero occorrenze del token. Il resto basta per trattarlo come configurazione segreta: inserire execution.log in .gitignore accanto a .env. Inoltre .gitignore non controlla l'accesso: non limita permessi, non elimina dopo un termine e non oscura contenuti. Perciò lo schema richiede di mascherare il conto salvo gli ultimi quattro caratteri e registrare campi di una lista propria, non tutta la risposta. Prima del funzionamento autonomo vanno sistemate due cose. Il percorso: execution.log è relativo: il file nasce nella directory di lavoro del processo, non accanto allo script. Avviando da Utilità di pianificazione o cron — come discusso sotto — può essere molto diversa, e il registro fondamentale per il recupero non sarà dove lo si cerca. Usare un percorso assoluto dalla posizione dello script o dalla configurazione. La dimensione: con la raccomandazione precedente cresce senza limite, perché l'esempio non ruota i file. logging.handlers.RotatingFileHandler appartiene alla stessa libreria standard e, aggiungendo import logging.handlers— perché import logging da solo non espone il sottomodulo — sostituisce FileHandler senza cambiare il resto della configurazione.Lo stato deve sopravvivere al riavvio. Il processo si fermerà per un aggiornamento, un blackout o il proprio Ctrl+C. Se le informazioni sugli ordini restano solo in memoria, al riavvio il programma non sa cosa abbia fatto. Il minimo è salvare gli identificativi su file o database prima dell'invio e confrontarli all'avvio con lo stato del conto dall'API. Evita doppi ingressi dopo una disconnessione.
Test senza rete. Logica e limiti si testano su dati preparati senza rete, come le barriere precedenti. Un test che richiede l'API non è unitario né eseguibile a ogni modifica. Iniziare da condizione d'ingresso, dimensionamento e comportamento con dati vuoti o lacune.
Arresto controllato. Il programma dovrebbe reagire alla terminazione: completare l'operazione, salvare lo stato e uscire, non interrompersi durante l'invio. A parte, conviene fermare gli ingressi superata la perdita giornaliera. Soglia e calcolo sono trattati nella parte sulla gestione del rischio; nel codice è una condizione prima di ogni ingresso, non una funzione di libreria.
cron su Unix avviano periodicamente e terminano dopo l'esecuzione; un servizio di sistema (per esempio un'unità systemd) mantiene il processo e può riavviarlo; un gestore di processi fa lo stesso a livello applicativo. Scegliere fra breve esecuzione e processo continuo decide dove conservare lo stato e chi garantisce una sola istanza. Uno script pianificato deve tollerare il precedente ancora attivo: controllare lo stato prima di agire, non presumere di partire da zero.Si veda anche: Trading algoritmico: architettura del sistema e ruolo dell'automazione nell'intero processo decisionale.
9. Look-ahead e fuga di dati nel codice Python
Il look-ahead non è un errore metodologico: è un errore in una riga di codice, perciò appartiene qui e non all'articolo metodologico. Un risultato assegnato a un momento usa informazioni allora inesistenti.
La forma più comune in pandas è banale: segnale per rendimento della stessa riga. Il rendimento nella riga t si è prodotto fra la chiusura precedente e quella della candela t. Un segnale dalla chiusura della candela t è noto solo dopo quel rendimento. Moltiplicarli attribuisce un movimento sconosciuto al momento della decisione.
Ipotesi dell'esempio: otto osservazioni orarie sintetiche, indice UTC, valori illustrativi e nessun costo: si mostra lo spostamento, non si valuta una regola. Ambiente come sezione 2: Python 3.12.10, pandas 3.0.5.
"""Look-ahead in una riga: stessa regola, due risultati."""
import pandas as pd
index = pd.date_range("2026-06-01", periods=8, freq="1h", tz="UTC")
df = pd.DataFrame(
{"close": [1.0800, 1.0810, 1.0825, 1.0815, 1.0830, 1.0845, 1.0840, 1.0855]},
index=index,
)
df["sma"] = df["close"].rolling(3).mean()
df["return"] = df["close"].pct_change()
# Regola: long se la chiusura supera la media di tre candele.
df["position"] = (df["close"] > df["sma"]).astype(int)
# ERRATO: posizione della riga t per rendimento della stessa riga.
# Il rendimento t nasce fra chiusura t-1 e chiusura t; la decisione
# usa quindi un prezzo ancora inesistente all'ingresso.
df["result_with_lookahead"] = df["position"] * df["return"]
# CORRETTO: una posizione decisa alla chiusura t agisce sul rendimento t+1.
df["result_without_lookahead"] = df["position"].shift(1) * df["return"]
print(df[["result_with_lookahead", "result_without_lookahead"]].sum().round(6))
Output dell'esecuzione:
result_with_lookahead 0.005082
result_without_lookahead 0.001384
dtype: float64
Interpretazione. Stessa regola, stessi dati, una chiamata a shift(1) di differenza: la somma dei rendimenti semplici scende da 0,005082 a 0,001384, sotto il 30 per cento iniziale. Otto osservazioni illustrano, non misurano; su anni di dati l'errore può rendere una strategia media spettacolare e una spettacolare impossibile. I valori sono adimensionali, senza spread o commissione; con costi la versione corretta sarebbe ancora inferiore.
Una riserva sull'aritmetica. pct_change() dà rendimenti semplici che, come indicato nella sezione 4, si compongono moltiplicando, non sommando; solo quelli logaritmici possono essere sommati. Qui la somma è un’approssimazione deliberata: con valori dell’ordine dei millesimi, la differenza fra somma e composizione corretta è inferiore a 0,00001, oltre due ordini di grandezza minore della differenza che si vuole mostrare. Per descrivere il risultato del conto, non il meccanismo dello spostamento, comporre i fattori (1 + r) o passare ai logaritmici e solo allora sommare.
Attenzione al doppio spostamento. Spostare in un punto: posizione o dati del segnale, mai entrambi. Spostare i prezzi e poi la posizione ritarda di due periodi e sottostima silenziosamente il risultato. Meno pericoloso del look-ahead, ma ugualmente falso.
Altre forme di look-ahead in Python: sempre la stessa domanda: il valore era noto alla decisione?
- Usare massimo o minimo della candela corrente per entrare, sebbene siano noti solo dopo la chiusura
- Scalare o normalizzare tutto prima della divisione addestramento/validazione: i parametri portano informazioni future
- Indicatori che smussano o segnano svolte con candele successive: il valore storico differisce da quello dal vivo
- Dati macro rivisti anziché quelli pubblicati originariamente
- Eseguire alla chiusura della candela del segnale
Fuga di dati nella preparazione del dataset. Il secondo punto merita chiarezza: in Python viene spontaneo. Applicare uno scaler all'intero DataFrame prima della divisione sembra innocuo, ma trasferisce la distribuzione futura alla validazione. Determinare le trasformazioni solo sui dati precedenti e applicarle ai successivi, mai viceversa.
Test multipli. Più varianti, più probabilità che la migliore sia casuale. Sotto un modello nullo esplicito, 10.000 test con probabilità di falso positivo 0,05 danno 0,05 × 10.000 = 500 risultati «significativi» attesi. Si sommano probabilità senza richiedere indipendenza: la dipendenza cambia la distribuzione del conteggio, non la media sotto quelle ipotesi. Perciò si dichiara il numero di tentativi.
Esiste una correzione formale per i tentativi: il Deflated Sharpe Ratio, proposto da Bailey e López de Prado nel 2014 [12]. Prima di usare un'implementazione, verificare stato e licenza: alcuni progetti hanno cambiato distribuzione. La formula è nella pubblicazione originale ed è implementabile autonomamente.
Cosa non copre questa sezione. Overfitting, selezione del campione, walk-forward, bias di sopravvivenza e robustezza ai costi sono metodologia, descritta nell'articolo su backtesting e forward test. Qui conta solo cosa si può sbagliare e verificare nel codice.
Verificare il codice prima di fidarsi:
- Il risultato della riga t usa solo dati disponibili prima della decisione?
- Spostamento applicato una volta: segnale o dati, non entrambi?
- Momento e prezzo di esecuzione sono espliciti?
- Costi inclusi o omessi consapevolmente e dichiarati?
- Addestramento e validazione sono divisi cronologicamente?
- Trasformazioni determinate solo sulla parte precedente?
- Numero di varianti registrato insieme al risultato?
- Indice con fuso esplicito, crescente e senza duplicati?
10. Quanto codice serve davvero
Non serve diventare sviluppatori, ma sapere abbastanza da non farsi ingannare dal proprio codice o da script scaricati.
| Livello | Competenze | Cosa offre | Tempo indicativo |
|---|---|---|---|
| 0 – nessun codice | Nessuna | Si analizza ciò che permette la piattaforma, come impone essa | — |
| 1 – leggere codice | Capire script altrui, cambiare parametri, eseguirli isolati | Verificare look-ahead e costi negli script dei repository | Qualche settimana |
| 2 – analisi propria | pandas, numpy, matplotlib; propri script analitici | Analisi di diario e mercato senza i limiti del foglio | Qualche mese |
| 3 – integrazione | API, gestione errori, registri, pianificazione, test | Notifiche e preparazione ordini con conferma | Sei mesi o più |
| 4 – codice di esecuzione | Test automatici, stato, distribuzioni, monitoraggio | Programma senza confermare ogni decisione | Anni di pratica |
Il tempo è indicativo e dipende dal punto di partenza e dalle ore settimanali effettive: ordine di grandezza, non promessa.
I livelli 1 e 2 non danno un vantaggio sul mercato. Consentono però di valutare soluzioni altrui e smettere di comprare sistemi non verificabili. È valore reale, diverso da quello venduto dai fornitori. Il livello 4 serve a mantenere programmi di esecuzione, non a negoziare meglio. Il manuale di Hilpisch offre una panoramica di Python in finanza [11], con la riserva di ogni libro sul codice: verificare i frammenti con le versioni attuali, perché alcune interfacce sono cambiate.
Ordine sensato di apprendimento: ogni passo si basa sul precedente e finisce con qualcosa che funziona:
- Fondamenti del linguaggio: variabili, liste, funzioni, condizioni; abbastanza per leggere senza indovinare
- pandas sui propri dati: caricare il diario e calcolare per giorno e strumento. Se si preferiscono libri alla documentazione, McKinney, autore di pandas, copre interamente questa fase [10]
- Un grafico: tracciare patrimonio e drawdown dai calcoli precedenti
- Verificare qualità dei dati: ripetere la sezione 4 sul proprio file: fuso, duplicati, lacune
- Integrazione di sola lettura: dati API in prova con token senza permessi transazionali, se distinti dal fornitore
L'ordine è deliberato: il quinto passo arriva quando si sanno verificare i dati, altrimenti si automatizza un errore.
Iniziare caricando dati propri, calcolare qualcosa verificabile a mano su tre righe e confrontare. Se coincide col contare sulle dita, c'è una base; altrimenti si è trovato il primo errore: meglio di uno script funzionante ma incomprensibile.
Python non ripara una strategia cattiva; al massimo lo mostra prima.
FAQ – Python nel trading
Python è abbastanza veloce per automatizzare il trading?
Quanto serve per imparare Python a un livello utile al trading?
Posso collegare Python a un conto MT4 o MT5?
MetaTrader5 installabile con pip, ma solo su Windows. Su macOS e Linux non esiste installazione ufficiale: occorre eseguire terminale e Python sotto Wine o creare un livello intermediario. [FUNZIONE DIPENDENTE DA VERSIONE/AMBIENTE]. Il pacchetto comunica con un terminale MetaTrader 5 sullo stesso computer, non direttamente col broker. Deve essere installato, non necessariamente avviato: la documentazione dice che la chiamata initialize() lo avvia se necessario; i parametri di accesso sono facoltativi e, se assenti, usa i dati salvati nel terminale. [18]. Non è cosmetico: uno script pianificato può avviare il terminale e accedere senza intervento. È comodo, ma «il terminale era chiuso» non protegge. L'integrazione arrivò nel 2019, build 2085 [7]. Non è un'API REST indipendente dal broker. Un ponte cloud alternativo aggiunge un anello e affida l'accesso a terzi. L'inizio più semplice e sicuro resta unidirezionale: esportare, analizzare in Python, decisione ed esecuzione umane.Dove conservare chiavi API e cosa fare se trapelano?
.gitignore. Mai nel codice, commenti o notebook: la cella salva output e il token stampato resta nel file. Se arriva nel repository, cancellare la riga non elimina la cronologia. Revocare la chiave presso il broker, generarne una nuova e poi pulire la cronologia. Conviene anche limitare dimensioni sul conto come seconda protezione indipendente dal codice.Il codice dell'articolo è stato verificato?
127.0.0.1 né verso un broker. Sono stati verificati gli scenari documentati di gestione delle risposte, inclusi l'esaurimento dei tentativi e un'eccezione di connessione. L'articolo non contiene però una sola riga che invii un ordine: l'invio è stato sostituito da uno schema strutturale, perché le transazioni non si testano sui conti altrui e una funzione pronta senza test insegnerebbe solo fiducia nel codice non verificato. Eseguire codice non conferma ipotesi sui server altrui: verificare percorsi, strutture delle risposte e limiti nella documentazione del proprio broker e su un conto di prova.Posso fidarmi di una strategia in un repository pubblico?
Fonti e riferimenti
- Documentazione pandas 3.0.5 — pandas.pydata.org/docs: strutture dati, indice temporale e fusi,
resample, valori mancanti; versione verificata il 2026-08-22 - pandas — note di rilascio 2.2.0 e 3.0.0 — pandas.pydata.org/docs/whatsnew: deprecazione del parametro
methodinfillna()e cambiamenti del comportamento predefinito - NumPy — documentazione e note di rilascio — numpy.org/doc: vettorizzazione, generatori casuali e semi
- Documentazione Python — docs.python.org/3: moduli
venv,loggingetimeite gestione delle eccezioni - OANDA v20 — buone pratiche — developer.oanda.com/rest-live-v20/best-practices: frequenza raccomandata delle richieste su connessione esistente (100/s) e nuove connessioni (2/s); verificato il 2026-08-22
- MQL5 — integrazione Python — mql5.com/en/docs/python_metatrader5: pacchetto
MetaTrader5e comunicazione con il terminale - MetaTrader 5 — note della build 2085 — metatrader5.com/en/releasenotes/terminal/2085: introduzione dell'integrazione Python nel terminale
- cTrader Algo — documentazione — help.ctrader.com/ctrader-algo: supporto nativo C# e Python per cBot, indicatori e plugin; stato al 2026-08-22
- TA-Lib (collegamenti Python) — pypi.org/project/TA-Lib: pacchetti binari su PyPI, installazione senza compilare C
- McKinney, W. – Python for Data Analysis, O'Reilly, 3ª edizione, 2022 — pandas, numpy, serie temporali
- Hilpisch, Y. – Python for Finance, O'Reilly, 2ª edizione, 2018 — Python in finanza; verificare codice con versioni attuali
- Bailey, D. H., López de Prado, M. – The Deflated Sharpe Ratio, Journal of Portfolio Management, 2014 — correzione Sharpe per varianti testate
- PyPI — pagine dei pacchetti e interfaccia JSON del registro — pypi.org: date e numeri delle ultime versioni citate (
mplfinance,streamlit,openpyxl,pandas-ta,backtrader,vectorbt,backtesting.py,ib_insync,ib_async); verificato il 2026-10-08 - OANDA v20 — API ordini e transazioni — developer.oanda.com/rest-live-v20/transaction-df –
clientExtensionscome dati personalizzati facoltativi dell'ordine e avvertenza sui conti MT4; nessuna garanzia documentata d'idempotenza per ordini a mercato; verificato il 2026-08-22 - OANDA v20 — guida per sviluppatori — developer.oanda.com/rest-live-v20/development-guide: limite imposto REST di 120 richieste al secondo con HTTP 429, 20 flussi attivi e 2 nuove connessioni al secondo, per IP richiedente; verificato il 2026-08-22
- Microsoft — specifiche e limiti Excel — support.microsoft.com: foglio di 1.048.576 righe per 16.384 colonne; verificato il 2026-08-22
- Banca nazionale svizzera — comunicato del 15 gennaio 2015 — snb.ch: rimozione del cambio minimo di 1,20 franchi per euro e taglio a −0,75 per cento
- MQL5 —
MetaTrader5.initialize()– mql5.com/en/docs/python_metatrader5/mt5initialize_py: avvio del terminale alla connessione se necessario e parametri facoltativipath,login,passwordeservercon dati salvati nel terminale; verificato il 2026-08-22