libitofin: port QuantLib w języku Rust
Analiza projektu libitofin jako portu QuantLib w języku Rust z dostępem przez Python. Wyjaśnienie przewagi bibliotek wyceniających nad arkuszami oraz znaczenie wersji.
QuantLib w języku Rust to najkrótszy opis projektu libitofin: portu biblioteki QuantLib – otwartoźródłowego standardu w wycenie instrumentów pochodnych od początku lat dwutysięcznych – na język Rust, wraz z pakietem itofin dla języka Python. Według stanu na sierpień 2026 r. projekt znajduje się w fazie przed wersją 1.0, co determinuje wszelkie aspekty praktycznego korzystania z niego. Biblioteka wyceniająca zyskuje na wartości dzięki mechanizmom otaczającym wzory matematyczne, a te mechanizmy wymagają takiej samej pracy w każdym języku programowania.
Co biblioteka wyceniająca oferuje w przeciwieństwie do formuły w arkuszu kalkulacyjnym
Komórka z modelem Blacka-Scholesa w arkuszu kalkulacyjnym przyjmuje pięć danych wejściowych i zwraca cenę. Wzór jest najprostszą częścią zadania. Biblioteka to cztery warstwy, które go otaczają.
Struktury terminowe. Stopa dyskontowa to krzywa rozciągająca się wzdłuż terminów zapadalności, z regułą interpolacji dla luk między punktami, dla których istnieją kwotowania. Poniższy panel przedstawia materiał źródłowy: kwotowane rentowności obligacji skarbowych w określonym dniu.
Dokładny kod SQL dla każdej liczby
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 iWedług stanu na Aug 10, 2026 kwotowana krzywa obejmowała 7 terminów zapadalności, od 3.79% przy 1 month do 5.25% przy 30 years. Arkusz kalkulacyjny rozwiązuje to za pomocą wyszukiwania i wpisanej na sztywno wartości 4%. Biblioteka wykorzystuje obiekt krzywej, na podstawie którego wyceniany jest każdy instrument, przy zastosowaniu określonej interpolacji (liniowa dla stóp zerokuponowych, logarytmicznie liniowa dla czynników dyskontujących, splajny monotoniczne) oraz polityki ekstrapolacji poza ostatni punkt. Przesunięcie tego obiektu o jeden punkt bazowy powoduje spójną aktualizację wszystkich wrażliwości w portfelu.
Konwencje zliczania dni (day-count conventions). Odsetki narastają w ułamku roku, a definicja tego ułamka jest konwencją przypisaną do instrumentu. Metoda Actual/360 dzieli liczbę dni przez 360. Actual/365 dzieli przez 365. Rodzina 30/360 zakłada, że każdy miesiąc ma 30 dni. Metoda Business/252 zlicza sesje giełdowe w odniesieniu do roku 252-dniowego, co wymaga kalendarza giełdowego z uwzględnieniem świąt. Przyjmijmy hipotetyczny milion dolarów pożyczony na 5% na 90 dni: przy metodzie actual/360 narasta 12 500 USD, a przy actual/365 – 12 329 USD dla tej samej transakcji. Poniższy panel pokazuje, dlaczego baza dni roboczych wymaga kalendarza, a nie dzielnika.
Dokładny kod SQL dla każdej liczby
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 monthW okresie 12 miesięcy, 2026-07 obejmowało 22 sesji w porównaniu do 31 dni kalendarzowych. Żadna reguła arytmetyczna nie wygeneruje tej pierwszej liczby. Wynika ona z kalendarza świąt, a każdy miesiąc ma własny wynik. Biblioteka dostarcza takie kalendarze dla każdej giełdy i kraju; arkusz kalkulacyjny wymaga samodzielnego ich utrzymywania.
Mechanizmy kalibracji. Parametry modelu nie są nigdzie kwotowane. Wybiera się je poprzez dopasowanie cen modelowych do obserwowanych cen rynkowych na całej powierzchni kwotowań, a następnie ponowne dopasowanie w miarę zmian tej powierzchni. Jest to problem ograniczonej metody najmniejszych kwadratów, a biblioteka dostarcza niezbędną pętlę: optymalizator Levenberga-Marquardta, transformacje parametrów utrzymujące wariancję powyżej zera bez sztywnych ograniczeń, funkcję kosztu dla zbioru kwotowań oraz kryteria zbieżności, które kończą się błędem zamiast zwracać błędne wartości początkowe.
Warstwa numeryczna. Pod wszystkim znajduje się algebra liniowa i całkowanie. Rozkłady QR i SVD rozwiązują układy równań generowane przez dopasowanie, kwadratury i całkowanie Fouriera wyceniają modele oparte na całkach, a algorytmy znajdowania pierwiastków wyznaczają zmienność implikowaną. Ta warstwa jest techniczna i to właśnie ona jest najczęściej implementowana nieprawidłowo. Klasycznym przykładem jest ręcznie pisany solver Newtona, który jest zbieżny dla płynnych kwotowań at-the-money, ale traci stabilność przy głębokich opcjach out-of-the-money. Drugim błędem jest odwracanie macierzy, które traci precyzję przy blisko-osobliwym dopasowaniu i zwraca pozornie wiarygodne parametry. Jeśli budujesz model od podstaw, otwartoźródłowa książka o tradingu ilościowym jest lepszym punktem wyjścia niż dokumentacja API jakiejkolwiek biblioteki.
Czym jest libitofin i czy to QuantLib w języku Rust?
Tak, w istotnym znaczeniu. Jest to port architektury QuantLib na język Rust, a projekt deklaruje, że jest testowany przy użyciu własnego zestawu testów QuantLib. To uczciwe podejście do portowania biblioteki numerycznej: wyniki są sprawdzane względem implementacji referencyjnej, a nie względem subiektywnych oczekiwań. Ścieżka dla języka Python to pakiet itofin, dzięki czemu proces w Pythonie może korzystać z silnika w Rust bez konieczności posiadania łańcucha narzędzi C++.
Kluczowe jest oznaczenie przed wersją 1.0. Zgodnie z konwencją wersjonowania w Rust, wydanie 0.x nie daje gwarancji kompatybilności między wersjami mniejszymi: wersja 0.4 może zmienić, przenieść lub usunąć dowolny element w stosunku do 0.5. API należy traktować jako cel ruchomy, co wymusza określone nawyki:
- Przypinaj konkretną wersję w pliku blokady (lockfile) i aktualizuj celowo, używając własnego zestawu testów jako bramki.
- Utrzymuj cienką warstwę własnych wrapperów wokół typów biblioteki, aby zmiana nazwy wymagała poprawki w jednym pliku, a nie w czterdziestu.
- Rejestruj wersję biblioteki obok wygenerowanych liczb, aby ponowne uruchomienie z innym wynikiem wskazywało na aktualizację, a nie na rynek.
- Utrzymuj dane dotyczące marż produkcyjnych, zabezpieczeń i ryzyka regulacyjnego w systemach gwarantujących kompatybilność, dopóki nie pojawi się wersja 1.0.
Nie jest to krytyka projektu. „Przed 1.0” to dokładny opis i właściwe miejsce dla młodego portu tak dużej biblioteki. Błędem byłoby traktowanie jej jak gotowego zamiennika QuantLib i napotkanie zmienionej sygnatury w trakcie kwartału. Instrukcje instalacji również zmieniają się wraz z wersją, a zarówno cargo, jak i pip wymagają dostępu do sieci, więc należy czytać plik README projektu dla konkretnej wersji, zamiast kopiować fragmenty z blogów.
Python, skompilowany silnik czy Rust?
Dwa pytania rozstrzygają ten dylemat i żadne nie dotyczy preferencji. Ile razy na sekundę dokonujesz wyceny i czy wyniki muszą być zgodne między różnymi procesami? Rozmiar łańcucha opcji określa skalę pierwszego pytania.
Dokładny kod SQL dla każdej liczby
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 DESCW dniu Aug 11, 2026, SPY odnotowało 5336 różnych kontraktów w jednej sesji, w porównaniu do 360 dla KO. Wycena szerokiego łańcucha raz na jakiś czas nie stanowi problemu. Wycena z pięcioma wrażliwościami na kontrakt, przy każdej aktualizacji kwotowań, dla całego portfela instrumentów bazowych, to inny program o innych ograniczeniach.
Pozostań przy Pythonie i uznanej bibliotece, gdy pętla wyceny mierzona jest w tysiącach na minutę, a praca polega na badaniach, analizach lub wycenach na koniec dnia. Powiązania QuantLib dla Pythona to dojrzały wybór: ten sam silnik C++, najszersze pokrycie instrumentów i lata doświadczeń produkcyjnych. Szybkość rzadko jest ograniczeniem w kodzie badawczym; liczy się pokrycie i poprawność.
Wywołuj skompilowany silnik z Pythona, gdy pętla jest intensywna, a otaczający ją kod – nie. Kosztem jest sama granica między językami. Wywołanie z Pythona dla każdego kontraktu z osobna generuje narzut przy każdym przejściu; rozwiązaniem jest przekazanie silnikowi tablicy i odebranie tablicy wyników. To nisza, w którą celuje itofin, obok QuantLib-Python.
Wybierz Rust, gdy pętla wyceny jest samym produktem: wyceniacz wewnątrz serwisu kwotowań, obliczenia ryzyka w sztywnym harmonogramie lub binarka wysyłana na serwer bez zainstalowanego Pythona. Drugie pytanie również prowadzi do tego wniosku. Wyniki zmiennoprzecinkowe zależą od kolejności operacji, więc ten sam model zaimplementowany dwukrotnie może różnić się na ostatnich cyfrach, a notatnik badawczy niezgodny z serwisem produkcyjnym to tydzień pracy nad wyjaśnieniem rozbieżności. Jeden silnik używany po obu stronach eliminuje tę kategorię błędów, co jest głównym argumentem za skompilowanym rdzeniem z powiązaniami, niezależnie od języka. Ten sam instynkt dotyczy strategii, co pokazuje powtarzalny backtest.
Gdzie faktycznie znajduje się cel kalibracji
Kalibracja wymaga punktu odniesienia, a jest nim powierzchnia zmienności implikowanych przez rynek. Aspekt znajdowania pierwiastków został omówiony w sposobie obliczania zmienności implikowanej. Poniższy kształt jest tym, co model musi odwzorować.
Dokładny kod SQL dla każdej liczby
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)W pobliżu ceny wykonania (near-the-money), kontrakty na AAPL w koszyku 0 to 7 days miały średnią zmienność implikowaną 29.6% w obserwowanych sesjach, w porównaniu do 29.3% przy 241 days or more. Model z jednym parametrem zmienności nie może dopasować się do obu punktów jednocześnie, co jest powodem istnienia modeli ze strukturą terminową zmienności. Dopasowanie modelu do takiej powierzchni to etap kalibracji, a wrażliwości wynikające z dopasowanego modelu to greki, omówione w wyjaśnieniu greków opcyjnych.
Jak zbudowano te panele
Panel krzywej odczytuje najnowszą datę kwotowań z serii obligacji skarbowych i rozwija jedenaście kolumn terminów zapadalności w wiersze, pomijając terminy bez kwotowań w danym dniu. Panel sesji zlicza unikalne daty na taśmie SPY w miesiącu, co stanowi dobry przybliżony wskaźnik pełnej sesji giełdowej. Panel łańcucha zlicza unikalne kody kontraktów z wolumenem niezerowym w ostatniej dostępnej sesji, pogrupowane według instrumentu bazowego. Panel zmienności uwzględnia tylko zbieżne obliczenia z niezerowym wolumenem i cenami wykonania w zakresie 5% od ceny zamknięcia instrumentu bazowego, co stanowi standardowy zakres near-the-money; pierwszy koszyk obejmuje wygasające tego samego dnia.
FAQ
Czy libitofin to gotowy zamiennik QuantLib?
Nie. Według stanu na sierpień 2026 r. jest to port w fazie przed wersją 1.0, który pokrywa część funkcjonalności QuantLib i weryfikuje wyniki względem własnego zestawu testów QuantLib. Sam QuantLib, dostępny przez powiązania dla Pythona, pozostaje szerszą i bardziej stabilną opcją w zastosowaniach produkcyjnych.
Co oznacza „przed 1.0” dla biblioteki wyceniającej?
Zgodnie z konwencją wersjonowania w Rust, wydanie 0.x nie daje gwarancji kompatybilności: kolejna wersja mniejsza może zmienić lub usunąć dowolny element. W praktyce oznacza to przypięcie konkretnej wersji i ponowne uruchamianie własnych testów przy każdej aktualizacji.
Czy muszę znać Rust, aby używać libitofin?
Nie. Projekt publikuje pakiet itofin dla Pythona, więc silnik jest wywoływalny z poziomu standardowego procesu w Pythonie. Znajomość Rust staje się istotna, gdy to sama pętla wyceny jest produktem, który dostarczasz.
Co biblioteka wyceniająca daje mi w przeciwieństwie do formuły w arkuszu?
Krzywe zamiast pojedynczych stóp, konwencje zliczania dni z rzeczywistymi kalendarzami giełdowymi, pętlę kalibracji dopasowującą parametry modelu do cen rynkowych oraz przetestowaną warstwę numeryczną pod spodem. Wzór matematyczny to tylko niewielka część zadania.
Czy język programowania zmienia cenę opcji?
Matematycznie nie. Zmienia jednak powtarzalność: wyniki zmiennoprzecinkowe zależą od kolejności operacji, więc dwie implementacje tego samego modelu mogą różnić się na ostatnich cyfrach. Używanie tego samego silnika w badaniach i produkcji eliminuje tę rozbieżność.
Każdy panel zawiera odpowiadające mu zapytanie SQL, więc rozwiń je, aby zobaczyć, jak dokonano obliczeń. Te same pytania o krzywe, kalendarze i łańcuchy można zadać w języku naturalnym na terminalu Strasmore.