Strasmore Research
למידה Matt Connorמאת Matt Connor · data as of August 12, 2026 · refreshed weekly

libitofin: פורט של QuantLib לשפת Rust

הכירו את libitofin, ספריית תמחור פיננסי ב-Rust הזמינה לשימוש ב-Python. גלו מדוע ספרייה עדיפה על גיליון אלקטרוני ומה החשיבות של עבודה עם גרסאות מתויגות בפיתוח תוכנה.

מה מעניקה לך ספריית תמחור שנוסחת גיליון אלקטרוני אינה מעניקה

תא Black-Scholes בגיליון אלקטרוני מקבל חמישה קלטים ומחזיר מחיר. הנוסחה היא החלק הקל. סביבה קיימות ארבע שכבות, והשכבות הללו הן הספרייה.

מבני טווחים (Term structures). שיעור היוון הוא עקומה לאורך טווחי פקיעה, עם כלל אינטרפולציה עבור הפערים שבין הנקודות שמישהו מצטט בפועל. הלוח להלן מציג את חומר הגלם: טווחי Treasury מצוטטים בתאריך יחיד.

שאילתהעקומת התשואות בתאריך הציטוט האחרון
ה-SQL המדויק מאחורי כל מספר
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

נכון ל-Aug 10, 2026, העקומה המצוטטת כללה 7 טווחים, החל מ-3.79% ב-1 month ועד 5.25% ב-30 years. גיליון אלקטרוני מתמודד עם זה באמצעות פונקציית חיפוש וערך קבוע של 4%. ספרייה מתמודדת עם זה באמצעות אובייקט עקומה שכל מכשיר מתמחר לפיו, תחת אינטרפולציה מוגדרת (ליניארית על ריביות אפס, לוג-ליניארית על גורמי היוון, או Monotone splines) ומדיניות אקסטרפולציה מוגדרת מעבר לנקודה האחרונה. הזזת אובייקט זה בנקודת בסיס אחת גורמת לכל רגישות (sensitivity) בספר להשתנות בהתאם באופן עקבי.

מוסכמות מניית ימים (Day-count conventions). ריבית נצברת לאורך שבר של שנה, והגדרת שבר זה היא מוסכמה הצמודה למכשיר. Actual/360 מחלק את הימים שחלפו ב-360. Actual/365 מחלק ב-365. משפחת 30/360 מניחה שלכל חודש יש 30 ימים. Business/252 סופר ימי מסחר מול שנה של 252 ימים, מה שדורש לוח שנה של בורסה אמיתית עם חגים טעונים מראש. קחו מיליון דולר מושאלים ב-5% ל-90 ימים: ב-actual/360 יצטברו 12,500 דולר וב-actual/365 יצטברו 12,329 דולר על אותה עסקה. הלוח להלן מראה מדוע בסיס ימי עסקים דורש לוח שנה ולא מחלק.

שאילתהימי מסחר מול ימים קלנדריים, לפי חודש
ה-SQL המדויק מאחורי כל מספר
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

לאורך ה-12 חודשים המוצגים, 2026-07 כלל 22 ימי מסחר מול 31 ימים בלוח השנה. שום כלל אריתמטי לא מפיק את המספר הראשון. הוא מגיע מלוח שנה של חגים, ולכל חודש יש תשובה משלו. ספרייה מספקת את לוחות השנה הללו לפי זירה ולפי מדינה; גיליון אלקטרוני דורש מכם לתחזק אותם.

מנגנון כיול (Calibration machinery). פרמטרים של מודל אינם מצוטטים בשום מקום. אתם בוחרים אותם על ידי התאמת מחירי המודל למחירי שוק נצפים על פני כל משטח הציטוטים, ואז מבצעים התאמה מחדש כשהמשטח זז. זוהי בעיית ריבועים פחותים חסומה, והספרייה מספקת את הלולאה סביבה: אופטימייזר מסוג Levenberg-Marquardt, טרנספורמציות פרמטרים ששומרות על שונות חיובית ללא אילוצים קשיחים, פונקציית עלות על פני קבוצת הציטוטים, וקריטריוני התכנסות שנכשלים באופן מובהק במקום להחזיר בשקט את ניחוש ההתחלה.

שכבת נומריקה. מתחת להכל יושבת אלגברה ליניארית ואינטגרציה. פירוקי QR ו-SVD פותרים את המערכות שהתאמה מייצרת, קוואדראטורה ואינטגרציית פורייה מתמחרים מודלים שהנוסחאות שלהם הן אינטגרלים, ופותרים נומריים (root finders) מחלצים את התנודתיות הגלומה. השכבה הזו משעממת, וזו בדיוק השכבה שנוטים ליישם מחדש בצורה גרועה. הגרסה הקלאסית היא פותר Newton שנכתב ידנית, שמתכנס בציטוטים נזילים בכסף (at-the-money) ומתפזר בציטוטים עמוק מחוץ לכסף (out-of-the-money). במקום השני נמצא היפוך מטריצה שמאבד דיוק בהתאמה כמעט-סינגולרית ומחזיר פרמטרים שנראים סבירים לחלוטין. אם אתם בונים את המודל המנטלי מאפס, ספר מסחר כמותי בקוד פתוח הוא נקודת התחלה טובה יותר מכל הפניה ל-API של ספרייה.

מהו libitofin, והאם הוא QuantLib ב-Rust?

כן, במובן שחשוב. זהו פורט של העיצוב של QuantLib ל-Rust, והפרויקט מצהיר שהוא נבדק מול חבילת הבדיקות של QuantLib עצמה. זו הדרך ההגונה לבצע פורט לספריית נומריקה: התוצאות נבדקות מול יישום הייחוס ולא מול ציפייה שנכתבה ידנית לגבי מה התשובה אמורה להיות. נתיב ה-Python הוא חבילה בשם itofin, כך שתהליך Python יכול להגיע למנוע ה-Rust ללא צורך ב-toolchain של C++ בלולאה.

התווית שחשובה יותר היא pre-1.0. תחת מוסכמת הגרסאות של Rust, גרסת 0.x אינה נושאת הבטחת תאימות בין גרסאות משניות: מ-0.4 ל-0.5 מותר לשנות שמות, להעביר או למחוק כל דבר. התייחסו ל-API כיעד נע, ובעקבות זאת יש לאמץ כמה הרגלים.

  • קבעו גרסה מתויגת מדויקת ב-lockfile שלכם ושדרגו במכוון, עם חבילת בדיקות משלכם כשער.
  • שמרו על מעטפת דקה משלכם סביב הטיפוסים של הספרייה, כך ששינוי שם יתבצע בקובץ אחד במקום בארבעים.
  • תעדו את גרסת הספרייה לצד המספרים שהיא הפיקה, כך שהרצה חוזרת שלא תואמת תצביע על השדרוג ולא על השוק.
  • שמרו נתוני מרווח ייצור, בטוחות וסיכונים רגולטוריים על משהו שנושא הבטחת תאימות עד להגעת גרסה 1.0.

שום דבר מזה אינו ביקורת על הפרויקט. Pre-1.0 הוא תיאור עצמי מדויק והמקום הנכון עבור פורט צעיר של ספרייה גדולה מאוד. מצב הכשל הוא קורא שמתייחס אליה כאל תחליף מיידי ל-QuantLib ונתקל בחתימה שהשתנתה בגרסה משנית, באמצע רבעון. הוראות התקנה משתנות גם הן עם הגרסה, וגם cargo וגם pip זקוקים לגישה לרשת כדי לפתור תלויות, לכן קראו את ה-README של הפרויקט עצמו בתג שאתם מתכוונים לקבע, ולא קטע שהועתק מפוסט בבלוג, כולל זה הנוכחי.

Python, מנוע מקומפל, או Rust?

שתי שאלות מכריעות זאת, ואף אחת מהן אינה עניין של טעם. כמה פעמים בשנייה אתם מתמחרים מחדש, והאם המספרים שלכם חייבים להתאים בין תהליכים נפרדים? גודל השרשרת קובע את קנה המידה של השאלה הראשונה.

שאילתהחוזי אופציות ייחודיים שנסחרו ביום מסחר אחד
ה-SQL המדויק מאחורי כל מספר
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

ב-Aug 11, 2026, ל-SPY היו 5336 חוזים שונים שנסחרו בסשן בודד, מול 360 עבור KO. תמחור השרשרת הרחבה פעם אחת הוא עניין של מה בכך. תמחור שלה עם חמש רגישויות לכל חוזה, בכל עדכון ציטוט, לאורך ספר של נכסי בסיס, הוא תוכנית אחרת עם אילוצים אחרים.

הישארו ב-Python עם ספרייה מבוססת כאשר הלולאה נמדדת באלפי הערכות לדקה והעבודה שמסביב היא מחקר, ניתוח או סימוני סוף יום. ה-bindings של QuantLib ל-Python הם הבחירה הבוגרת: אותו מנוע C++, הכיסוי הרחב ביותר של מכשירים, ושנים של ניסיון בייצור. מהירות היא לעיתים רחוקות האילוץ המגביל בקוד מחקר; כיסוי ונכונות הם כן.

קראו למנוע מקומפל מ-Python כאשר הלולאה "חמה" והקוד שמסביב אינו כזה. העלות שיש לשים לב אליה היא הגבול עצמו. קריאה לכל חוזה מ-Python משלמת תקורה על כל חצייה, והפתרון הוא להעביר למנוע מערך ולקבל מערך בחזרה. זהו המקום שאליו itofin מכוון, לצד QuantLib-Python.

כתבו ב-Rust כאשר לולאת התמחור היא המוצר: מתמחר בתוך שירות ציטוטים, הרצת סיכונים בלוח זמנים שאינכם יכולים להחמיץ, או קובץ בינארי שנשלח למכונה ללא Python עליה. השאלה השנייה נוחתת כאן גם כן. תוצאות נקודה צפה תלויות בסדר הפעולות, לכן אותו מודל שיושם פעמיים יכול להציג אי-הסכמה בספרות האחרונות, ומחברת מחקר שלא תואמת שירות ייצור היא שבוע של עבודת בילוש. מנוע אחד המשמש משני הצדדים מסיר את כל הקטגוריה הזו של אי-התאמות, וזהו הטיעון העמיד לליבה מקומפלת עם bindings לכל שפה שבה היא כתובה. אותו אינסטינקט חל על עבודת אסטרטגיה, כפי ש-backtest שניתן לשחזור מראה.

היכן יעד הכיול באמת נמצא

כיול זקוק למשהו להתאים מולו, והמשהו הזה הוא משטח של תנודתיות גלומה בשוק. הצד של פתרון המשוואות (root-finding) מכוסה ב-כיצד מחושבת תנודתיות גלומה. הצורה להלן היא מה שמודל חייב להתאים אליו.

שאילתהתנודתיות גלומה (IV) ל-AAPL בנכסי Near-the-money לפי זמן לפקיעה
ה-SQL המדויק מאחורי כל מספר
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

קרוב לכסף (near the money), חוזי AAPL ב-bucket של 0 to 7 days הציגו בממוצע 29.6% תנודתיות גלומה לאורך הסשנים המוצגים, מול 29.3% ב-241 days or more. מודל הנושא פרמטר תנודתיות אחד אינו יכול לשבת על שתי הנקודות בו-זמנית, וזו הסיבה המלאה לכך שמודלים עם מבנה טווחים של תנודתיות קיימים. התאמת אחד למשטח כזה היא שלב הכיול, והרגישויות שנובעות מהמודל המותאם הן ה-Greeks, המכוסים ב-הסבר על ה-Greeks של אופציות.

כיצד נבנו הלוחות הללו

לוח העקומה קורא את תאריך הציטוט האחרון ביותר בסדרת ה-Treasury ופורס את אחת-עשרה עמודות הטווחים שלו לשורות, תוך השמטת כל טווח ללא ציטוט באותו יום. לוח הסשנים סופר תאריכים שונים ב-tape של SPY לפי חודש, מהווה פרוקסי נקי לספירת ימי מסחר מלאים. לוח השרשרת סופר קודי חוזים שונים עם נפח שאינו אפס בסשן האחרון הזמין, מקובצים לפי נכס בסיס. לוח התנודתיות שומר רק פתרונות שהתכנסו עם נפח שאינו אפס ומחירי מימוש בטווח של 5% ממחיר הסגירה של נכס הבסיס, שהוא טווח ה-near-the-money הסטנדרטי; ה-bucket הקדמי כולל פקיעות באותו יום.

שאלות נפוצות

האם libitofin הוא תחליף מיידי ל-QuantLib?

לא. נכון לאוגוסט 2026, זהו פורט בגרסת pre-1.0 המכסה חלק מהשטח של QuantLib ומאמת את תוצאותיו מול חבילת הבדיקות של QuantLib עצמה. QuantLib עצמה, אליה מגיעים דרך ה-bindings ל-Python, נותרת האופציה הרחבה והיציבה יותר לעבודת ייצור.

מה המשמעות של pre-1.0 עבור ספריית תמחור?

תחת מוסכמת הגרסאות של Rust, גרסת 0.x אינה מבטיחה תאימות: הגרסה המשנית הבאה חופשית לשנות שם או למחוק כל דבר. בפועל, המשמעות היא קיבוע גרסה מתויגת מדויקת והרצה חוזרת של חבילת הבדיקות שלכם בכל שדרוג.

האם אני צריך לכתוב ב-Rust כדי להשתמש ב-libitofin?

לא. הפרויקט מפרסם חבילת Python בשם itofin, כך שהמנוע ניתן לקריאה מתהליך Python רגיל. כתיבה ב-Rust הופכת לרלוונטית כאשר לולאת התמחור עצמה היא הדבר שאתם מפיצים.

מה מעניקה לי ספריית תמחור שנוסחת גיליון אלקטרוני אינה מעניקה?

עקומות במקום ריביות בודדות, מוסכמות מניית ימים עם לוחות שנה בורסאיים אמיתיים מאחוריהן, לולאת כיול שמתאימה פרמטרים של מודל למחירים מצוטטים, ושכבת נומריקה בדוקה מתחת לכל השלושה. הנוסחה הסגורה היא החלק הקטן של העבודה.

האם שפת התכנות משנה את מחיר האופציה?

לא מתמטית. היא משנה את היכולת לשחזר תוצאות: תוצאות נקודה צפה תלויות בסדר הפעולות, לכן שני יישומים של מודל אחד יכולים להציג אי-הסכמה בספרות האחרונות. הרצת מחקר וייצור על אותו מנוע מסירה את הפער הזה.


כל לוח כאן נושא את ה-SQL המדויק שלו מתחתיו, לכן הרחיבו אחד כדי לראות כיצד נלקחה הספירה. את אותן שאלות על עקומה, לוח שנה ושרשרת ניתן לשאול באנגלית פשוטה במסוף Strasmore.