Strasmore Research
Formazione Matt ConnorDi Matt Connor · data as of August 12, 2026 · refreshed weekly

QuantLib in Rust: guida a libitofin e al porting

Analisi di libitofin, il porting di QuantLib in Rust accessibile via Python. Scopri perché una libreria di pricing supera il foglio di calcolo e l'importanza del versioning.

QuantLib in Rust è la descrizione sintetica di libitofin: un porting in Rust di QuantLib, la libreria C++ che dagli inizi degli anni duemila rappresenta il riferimento open source per il pricing dei derivati, corredata da un pacchetto Python denominato itofin. Ad agosto 2026 il progetto si definisce pre-1.0, un'etichetta che determina ogni aspetto pratico del suo utilizzo. Una libreria di pricing guadagna la propria posizione grazie all'infrastruttura che avvolge le formule, e tale infrastruttura richiede lo stesso lavoro in qualsiasi linguaggio.

Cosa offre una libreria di pricing che una formula su foglio di calcolo non può dare

Una cella Black-Scholes in un foglio di calcolo richiede cinque input e restituisce un prezzo. La formula è la parte semplice. Attorno ad essa risiedono quattro strati, e quegli strati costituiscono la libreria.

Strutture a termine. Un tasso di sconto è una curva lungo le scadenze, con una regola di interpolazione per gli intervalli tra i punti effettivamente quotati. Il pannello sottostante mostra la materia prima: i tenor dei Treasury quotati in una singola data.

QueryLa curva dei Treasury alla data di quotazione più recente
L'esatto SQL dietro ogni numero
SELECT
    arrayElement(tenors, i)                  AS tenor,
    round(arrayElement(rates, i), 2)         AS yield_pct,
    formatDateTime(curve_date, '%b %e, %Y')  AS as_of
FROM
(
    SELECT
        date AS curve_date,
        ['1 month', '3 months', '6 months', '1 year', '2 years', '3 years',
         '5 years', '7 years', '10 years', '20 years', '30 years']         AS tenors,
        [toFloat64(yield_1_month), toFloat64(yield_3_month), toFloat64(yield_6_month),
         toFloat64(yield_1_year),  toFloat64(yield_2_year),  toFloat64(yield_3_year),
         toFloat64(yield_5_year),  toFloat64(yield_7_year),  toFloat64(yield_10_year),
         toFloat64(yield_20_year), toFloat64(yield_30_year)]                AS rates,
        arrayJoin(range(1, 12))                                            AS i
    FROM global_markets.treasury_yields
    WHERE date = (SELECT max(date) FROM global_markets.treasury_yields)
)
WHERE yield_pct > 0
ORDER BY i
Run this yourself

Alla data Aug 10, 2026 la curva quotata presentava 7 tenor, spaziando da 3.79% al 1 month fino a 5.25% al 30 years. Un foglio di calcolo gestisce questo aspetto con una funzione di ricerca e un 4% cablato. Una libreria lo gestisce con un oggetto curva su cui ogni strumento effettua il pricing, secondo una specifica interpolazione (lineare sui tassi zero, log-lineare sui fattori di sconto, spline monotone) e una politica di estrapolazione definita oltre l'ultimo punto. Spostando quell'unico oggetto di un basis point, ogni sensibilità nel book si muove in modo coerente.

Convenzioni day-count. Gli interessi maturano su una frazione di anno, e la definizione di tale frazione è una convenzione legata allo strumento. Actual/360 divide i giorni trascorsi per 360. Actual/365 divide per 365. La famiglia 30/360 ipotizza che ogni mese abbia 30 giorni. Business/252 conta le sessioni di trading rispetto a un anno di 252 giorni, il che richiede un calendario di borsa reale con le festività già caricate. Si prenda un milione di dollari ipotetico preso in prestito al 5% per 90 giorni: l'actual/360 matura 12.500 dollari e l'actual/365 matura 12.329 dollari sulla stessa operazione. Il pannello sottostante mostra perché la base business-day necessita di un calendario anziché di un divisore.

QuerySessioni di trading rispetto ai giorni di calendario, per mese
L'esatto SQL dietro ogni numero
SELECT
    formatDateTime(toStartOfMonth(date), '%Y-%m')      AS month,
    countDistinct(date)                                AS trading_days,
    toUInt8(toDayOfMonth(toLastDayOfMonth(max(date)))) AS calendar_days
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
  AND date >= toStartOfMonth(today() - 365)
  AND date <  toStartOfMonth(today())
GROUP BY month
ORDER BY month
Run this yourself

Nei 12 mesi presi in esame, 2026-07 ha registrato 22 sessioni a fronte di 31 giorni di calendario. Nessuna regola aritmetica produce quel primo numero. Deriva da un calendario delle festività, e ogni mese ha la sua risposta. Una libreria fornisce tali calendari per sede e per paese; un foglio di calcolo richiede che sia l'utente a mantenerli.

Infrastruttura di calibrazione. I parametri del modello non sono quotati da nessuna parte. Si scelgono adattando i prezzi del modello ai prezzi di mercato osservati su un'intera superficie di quotazioni, per poi riadattarli al variare della superficie. Si tratta di un problema di minimi quadrati vincolati, e la libreria fornisce il ciclo di calcolo necessario: un ottimizzatore Levenberg-Marquardt, trasformazioni dei parametri che mantengono la varianza positiva senza vincoli rigidi, una funzione di costo sull'insieme delle quotazioni e criteri di convergenza che falliscono in modo esplicito anziché restituire silenziosamente la stima iniziale.

Uno strato numerico. Alla base di tutto risiedono l'algebra lineare e l'integrazione. Le decomposizioni QR e SVD risolvono i sistemi prodotti da un fit, la quadratura e l'integrazione di Fourier effettuano il pricing dei modelli le cui formule sono integrali, e i solutori di radici estraggono la volatilità implicita. Questo strato è noioso, ed è esattamente quello che viene reimplementato male. La versione classica è un solutore di Newton artigianale che converge sulle quotazioni at-the-money liquide e perde precisione su quelle deep out-of-the-money. Un secondo caso frequente è un'inversa di matrice che perde precisione su un fit quasi singolare e restituisce parametri che sembrano del tutto plausibili. Se si sta costruendo il modello mentale da zero, un libro di quant trading open source è un punto di partenza migliore rispetto a qualsiasi riferimento API di una libreria.

Cos'è libitofin, e si tratta di QuantLib in Rust?

Sì, nel senso che conta. È un porting del design di QuantLib in Rust, e il progetto dichiara di essere testato rispetto alla suite di test di QuantLib stessa. Questo è il modo corretto di effettuare il porting di una libreria numerica: i risultati vengono verificati rispetto all'implementazione di riferimento anziché rispetto a un'aspettativa scritta a mano di quale dovrebbe essere la risposta. Il percorso Python è un pacchetto chiamato itofin, in modo che un processo Python possa raggiungere il motore Rust senza una toolchain C++ nel ciclo.

L'etichetta che conta di più è pre-1.0. Secondo la convenzione di versionamento di Rust, una release 0.x non comporta alcuna promessa di compatibilità tra versioni minor: dalla 0.4 alla 0.5 è consentito rinominare, spostare o eliminare qualsiasi cosa. Si consideri l'API come un bersaglio mobile, e ne conseguono alcune abitudini.

  • Fissare una release specifica con tag nel proprio lockfile ed eseguire l'aggiornamento intenzionalmente, utilizzando la propria suite di test come filtro.
  • Mantenere un sottile wrapper personalizzato attorno ai tipi della libreria, in modo che una rinomina richieda modifiche in un solo file anziché in quaranta.
  • Registrare la versione della libreria accanto ai numeri prodotti, in modo che una riesecuzione discordante indichi l'aggiornamento anziché il mercato.
  • Mantenere i numeri relativi a margine di produzione, collaterale e rischio normativo su qualcosa che offra una promessa di compatibilità fino all'arrivo della versione 1.0.

Nulla di tutto ciò è una critica al progetto. Pre-1.0 è un'autodescrizione accurata e la collocazione corretta per un giovane porting di una libreria molto vasta. La modalità di fallimento è che un lettore la tratti come un sostituto immediato di QuantLib e si scontri con una firma modificata in una release minor, a metà trimestre. Anche le istruzioni di installazione cambiano con la versione, e sia cargo che pip necessitano di accesso alla rete per risolvere le dipendenze, quindi si legga il README del progetto al tag che si intende fissare, anziché un frammento copiato da un post di un blog, incluso questo.

Python, un motore compilato o Rust?

Due domande risolvono la questione, e nessuna riguarda il gusto personale. Quante volte al secondo si effettua il repricing, e i numeri devono coincidere tra processi separati? La dimensione della chain determina la scala della prima domanda.

QueryContratti di opzione distinti negoziati in una singola sessione
L'esatto SQL dietro ogni numero
WITH (SELECT max(date) FROM global_markets.options_greeks) AS last_session
SELECT
    underlying_symbol                      AS symbol,
    countDistinct(ticker)                  AS contracts_priced,
    formatDateTime(max(date), '%b %e, %Y') AS as_of
FROM global_markets.options_greeks
WHERE date = last_session
  AND underlying_symbol IN ('SPY', 'AAPL', 'NVDA', 'MSFT', 'KO')
  AND volume > 0
GROUP BY symbol
ORDER BY contracts_priced DESC
Run this yourself

In data Aug 11, 2026, SPY ha visto 5336 contratti distinti scambiati in una singola sessione, contro 360 per KO. Effettuare il pricing dell'intera chain una volta non è nulla. Effettuarlo con cinque sensibilità per contratto, a ogni aggiornamento di quotazione, su un intero book di sottostanti, è un programma diverso con vincoli diversi.

Si rimanga in Python con una libreria consolidata quando il ciclo è misurato in migliaia di valutazioni al minuto e il lavoro circostante è ricerca, analisi o mark di fine giornata. I binding Python di QuantLib sono la scelta matura: lo stesso motore C++, la più ampia copertura di strumenti e anni di utilizzo in produzione. La velocità è raramente il vincolo vincolante nel codice di ricerca; la copertura e la correttezza lo sono.

Si richiami un motore compilato da Python quando il ciclo è critico e il codice circostante non lo è. Il costo da monitorare è il confine stesso. Una chiamata per contratto da Python paga un overhead a ogni passaggio, e la soluzione è passare al motore un array e riceverne uno indietro. Questa è la collocazione a cui mira itofin, insieme a QuantLib-Python.

Si scriva in Rust quando il ciclo di pricing è il prodotto: un pricer all'interno di un servizio di quotazione, un calcolo del rischio su una pianificazione che non può mancare, o un binario distribuito su una macchina senza Python. La seconda domanda ricade qui. I risultati in virgola mobile dipendono dall'ordine delle operazioni, quindi lo stesso modello implementato due volte può divergere nelle ultime cifre, e un notebook di ricerca che non concorda con un servizio di produzione significa una settimana di analisi forense. Un unico motore utilizzato da entrambi i lati rimuove l'intera categoria di discrepanza, che è l'argomento solido a favore di un core compilato con binding, indipendentemente dal linguaggio in cui è scritto. Lo stesso istinto si applica al lavoro sulle strategie, come dimostra un backtest riproducibile.

Dove risiede effettivamente il target di calibrazione

La calibrazione necessita di un termine di confronto, e questo è rappresentato da una superficie di volatilità implicite di mercato. Il lato relativo alla ricerca delle radici è trattato in come viene calcolata la volatilità implicita. La forma sottostante è ciò che un modello deve replicare.

QueryVolatilità implicita near-the-money di AAPL per scadenza
L'esatto SQL dietro ogni numero
SELECT
    multiIf(days_to_expiry <=   7, '0 to 7 days',
            days_to_expiry <=  30, '8 to 30 days',
            days_to_expiry <=  60, '31 to 60 days',
            days_to_expiry <= 120, '61 to 120 days',
            days_to_expiry <= 240, '121 to 240 days',
                                   '241 days or more') AS dte_bucket,
    round(avg(implied_volatility) * 100, 1)            AS iv_pct,
    countDistinct(ticker)                              AS contracts
FROM global_markets.options_greeks
WHERE underlying_symbol = 'AAPL'
  AND date >= today() - 10
  AND days_to_expiry >= 0
  AND iv_converged = 1
  AND volume > 0
  AND abs(toFloat64(strike_price) / toFloat64(underlying_close) - 1) < 0.05
GROUP BY dte_bucket
ORDER BY min(days_to_expiry)
Run this yourself

Vicino al prezzo, i contratti AAPL nel bucket 0 to 7 days hanno registrato una volatilità implicita media del 29.6% nelle sessioni in esame, contro il 29.3% al 241 days or more. Un modello che trasporta un singolo parametro di volatilità non può posizionarsi su entrambi i punti contemporaneamente, motivo per cui esistono modelli con una struttura a termine della volatilità. Adattarne uno a una superficie come questa è il passaggio di calibrazione, e le sensibilità che derivano dal modello calibrato sono le greche, trattate in le opzioni greche spiegate.

Come sono stati costruiti questi pannelli

Il pannello della curva legge la singola data quotata più recente nella serie dei Treasury e srotola le sue undici colonne di tenor in righe, eliminando qualsiasi tenor privo di quotazione in quel giorno. Il pannello delle sessioni conta le date distinte sul nastro SPY per mese, che è un proxy pulito per un conteggio completo delle sessioni di borsa. Il pannello della chain conta i codici contratto distinti con volume non nullo nell'ultima sessione disponibile, raggruppati per sottostante. Il pannello della volatilità mantiene solo le risoluzioni convergenti con volume non nullo e strike entro il 5% della chiusura del sottostante, che è la banda standard near-the-money; il bucket frontale include le scadenze in giornata.

FAQ

libitofin è un sostituto immediato di QuantLib?

No. Ad agosto 2026 è un porting pre-1.0 che copre parte della superficie di QuantLib e convalida i propri risultati rispetto alla suite di test di QuantLib stessa. QuantLib, accessibile tramite i suoi binding Python, rimane l'opzione più ampia e stabile per il lavoro di produzione.

Cosa significa pre-1.0 per una libreria di pricing?

Secondo la convenzione di versionamento di Rust, una release 0.x non offre alcuna promessa di compatibilità: la versione minor successiva è libera di rinominare o rimuovere qualsiasi cosa. In pratica, ciò significa fissare una release specifica con tag ed eseguire nuovamente la propria suite di test a ogni aggiornamento.

Devo scrivere in Rust per utilizzare libitofin?

No. Il progetto pubblica un pacchetto Python denominato itofin, quindi il motore è richiamabile da un normale processo Python. Scrivere in Rust diventa rilevante quando il ciclo di pricing stesso è ciò che si sta distribuendo.

Cosa mi offre una libreria di pricing che una formula su foglio di calcolo non può dare?

Curve anziché tassi singoli, convenzioni day-count con calendari di borsa reali, un ciclo di calibrazione che adatta i parametri del modello ai prezzi quotati e uno strato numerico testato alla base di tutti e tre. La formula in forma chiusa è la parte minore del lavoro.

Il linguaggio di programmazione cambia il prezzo dell'opzione?

Non matematicamente. Cambia la riproducibilità: i risultati in virgola mobile dipendono dall'ordine delle operazioni, quindi due implementazioni dello stesso modello possono divergere nelle cifre finali. Eseguire ricerca e produzione dallo stesso motore elimina tale divario.


Ogni pannello qui riportato contiene il suo SQL esatto, quindi espandine uno per vedere come è stato effettuato il conteggio. Le stesse domande su curve, calendari e chain possono essere poste in inglese semplice sul terminale Strasmore.