بررسی کتابخانه libitofin برای قیمتگذاری در Rust
کتابخانه libitofin انتقال QuantLib به زبان Rust است که از طریق itofin در پایتون در دسترس قرار میگیرد. این مطلب مزایای کتابخانههای قیمتگذاری نسبت به صفحهگستردهها را بررسی میکند.
QuantLib در Rust توصیف تکخطی libitofin است: پورت (انتقال) کتابخانه QuantLib، که از اوایل دهه 2000 مرجع متنباز قیمتگذاری مشتقات در زبان C++ بوده، به زبان Rust؛ همراه با یک بسته پایتون به نام itofin که بر روی آن قرار دارد. تا اوت 2026، این پروژه خود را در وضعیت پیش از نسخه 1.0 توصیف میکند و همین برچسب، تمام جنبههای عملی استفاده از آن را تعیین میکند. یک کتابخانه قیمتگذاری، جایگاه خود را از طریق سازوکارهای پیرامون فرمولها به دست میآورد و این سازوکارها در هر زبانی، وظیفهای یکسان هستند.
آنچه یک کتابخانه قیمتگذاری به شما میدهد که فرمولهای صفحهگسترده نمیدهند
یک سلول Black-Scholes در یک صفحهگسترده (Spreadsheet) پنج ورودی میگیرد و یک قیمت برمیگرداند. فرمول، بخش ساده ماجراست. چهار لایه در اطراف آن قرار دارند که همان کتابخانه را تشکیل میدهند.
ساختارهای زمانی (Term structures). نرخ تنزیل، منحنیای در طول سررسیدهاست که برای فواصل بین نقاطی که واقعاً قیمتگذاری شدهاند، از قاعده درونیابی استفاده میکند. پنل زیر مواد خام این کار است: تنورهای (tenors) خزانهداری قیمتگذاریشده در یک تاریخ مشخص.
کد دقیق 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تا تاریخ Aug 10, 2026، منحنی قیمتگذاریشده شامل 7 تنور بود که از 3.79% با نرخ 1 month تا 5.25% با نرخ 30 years امتداد داشت. یک صفحهگسترده با یک جستجو و یک نرخ ثابت 4 درصد با این موضوع برخورد میکند. اما یک کتابخانه با آن به عنوان یک «شیء منحنی» برخورد میکند که هر ابزاری بر اساس آن قیمتگذاری میشود؛ با یک قاعده درونیابی مشخص (خطی بر نرخهای صفر، لگاریتمی-خطی بر عوامل تنزیل، یا اسپلاینهای یکنوا) و یک سیاست برونیابی مشخص برای نقاط پس از آخرین نقطه موجود. با تغییر دادن این شیء به اندازه یک واحد پایه (basis point)، تمام حساسیتهای موجود در دفتر بهطور هماهنگ با آن تغییر میکنند.
قراردادهای شمارش روز (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در طول 12 ماه مورد بررسی، 2026-07 دارای 22 جلسه معاملاتی در برابر 31 روز تقویمی بود. هیچ قاعده محاسباتی سادهای عدد اول را تولید نمیکند. این عدد از یک تقویم تعطیلات استخراج میشود و هر ماه پاسخ خاص خود را دارد. یک کتابخانه این تقویمها را به تفکیک هر بازار و هر کشور ارائه میدهد؛ در حالی که صفحهگسترده از شما میخواهد خودتان آنها را مدیریت کنید.
سازوکار کالیبراسیون. پارامترهای مدل در هیچجا به صورت مستقیم قیمتگذاری نمیشوند. شما آنها را با برازش قیمتهای مدل بر قیمتهای مشاهدهشده بازار در کل سطح قیمتها انتخاب میکنید و سپس با تغییر سطح، دوباره آنها را برازش میدهید. این یک مسئله حداقل مربعات محدود است و کتابخانه، حلقه پیرامون آن را فراهم میکند: یک بهینهساز Levenberg-Marquardt، تبدیلهای پارامتری که واریانس را بدون محدودیتهای سخت مثبت نگه میدارد، یک تابع هزینه بر مجموعه قیمتها، و معیارهای همگرایی که به جای بازگرداندن حدس اولیه، با صدای بلند شکست را اعلام میکنند.
لایه عددی. زیربنای همه اینها، جبر خطی و انتگرالگیری است. تجزیههای QR و SVD سیستمهای حاصل از برازش را حل میکنند، روشهای تربیع (quadrature) و انتگرالگیری فوریه مدلهایی را که فرمولهایشان انتگرالی است قیمتگذاری میکنند، و ریشهیابها (root finders) نوسانپذیری ضمنی (implied volatility) را استخراج میکنند. این لایه خستهکننده است و دقیقاً همان لایهای است که اغلب به شکلی ضعیف بازنویسی میشود. نسخه کلاسیک آن، یک حلکننده نیوتون دستساز است که در قیمتهای نقدِ «در سود» (at-the-money) همگرا میشود و در قیمتهای «خارج از سود» (out-of-the-money) به بیراهه میرود. مورد دوم، معکوس ماتریسی است که در برازشهای نزدیک به تکینگی (near-singular)، دقت خود را از دست میدهد و پارامترهایی کاملاً معقول اما غلط ارائه میدهد. اگر در حال ساخت مدل ذهنی از صفر هستید، یک کتاب متنباز معاملات کمی نقطه شروع بهتری نسبت به هر مرجع API کتابخانهای است.
libitofin چیست و آیا همان QuantLib در Rust است؟
بله، از جهتی که اهمیت دارد. این یک پورت از طراحی QuantLib به زبان Rust است و پروژه اعلام کرده که در برابر مجموعه تستهای خودِ QuantLib آزمایش شده است. این روش صادقانهای برای پورت کردن یک کتابخانه عددی است: نتایج به جای مقایسه با انتظارات ذهنیِ نویسنده، با پیادهسازی مرجع چک میشوند. مسیر پایتون، بستهای به نام itofin است، بنابراین یک فرآیند پایتون میتواند بدون نیاز به زنجیره ابزار C++ به موتور Rust دسترسی پیدا کند.
برچسبی که اهمیت بیشتری دارد، «پیش از نسخه 1.0» است. طبق قرارداد نسخهگذاری Rust، یک نسخه 0.x هیچ وعده سازگاری بین نسخههای فرعی (minor versions) نمیدهد: نسخه 0.4 به 0.5 مجاز است هر چیزی را تغییر نام دهد، جابهجا کند یا حذف کند. با API به عنوان یک هدف متحرک برخورد کنید و این چند عادت را دنبال کنید:
- یک نسخه تگشده دقیق را در فایل lockfile خود قفل کنید و ارتقا را آگاهانه انجام دهید، در حالی که مجموعه تستهای خودتان به عنوان دروازه عمل میکند.
- یک لایه نازک (wrapper) از نوعهای خودتان دورِ نوعهای کتابخانه نگه دارید تا تغییر نام در یک فایل اعمال شود، نه در چهل فایل.
- نسخه کتابخانه را در کنار اعدادی که تولید کرده ثبت کنید تا در صورت اجرای مجدد و بروز اختلاف، انگشت اتهام به سمت ارتقا باشد نه بازار.
- حاشیه سود تولید، وثیقه و اعداد ریسک نظارتی را روی چیزی نگه دارید که تا زمان رسیدن به نسخه 1.0، وعده سازگاری میدهد.
هیچکدام از اینها انتقاد به پروژه نیست. «پیش از 1.0» یک توصیف دقیق از خود پروژه است و جایگاه درستی برای پورتِ جوانِ یک کتابخانه بسیار بزرگ محسوب میشود. حالت شکست زمانی است که کاربر با آن به عنوان یک جایگزین آماده (drop-in) برای QuantLib برخورد کند و در میانه یک فصل، با تغییر امضای توابع در یک نسخه فرعی مواجه شود. دستورالعملهای نصب نیز با نسخه تغییر میکنند و هم cargo و هم pip برای حل وابستگیها به دسترسی شبکه نیاز دارند، بنابراین به جای کپی کردن قطعهکد از یک وبلاگ (از جمله همین متن)، README پروژه را در همان تگی که قصد قفل کردنش را دارید، مطالعه کنید.
پایتون، موتور کامپایلشده یا 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در تاریخ Aug 11, 2026، SPY دارای 5336 قرارداد متمایز در یک جلسه معاملاتی بود، در حالی که این عدد برای 360 برابر با KO بود. قیمتگذاری یک زنجیره گسترده برای یک بار، هیچ اهمیتی ندارد. قیمتگذاری آن با پنج حساسیت (sensitivity) به ازای هر قرارداد، در هر بهروزرسانی قیمت، در کلِ یک دفتر از داراییهای پایه، برنامهای متفاوت با محدودیتهای متفاوت است.
زمانی که حلقه محاسباتی در حد هزاران ارزیابی در دقیقه است و کارهای پیرامونی شامل تحقیق، تحلیل یا علامتگذاری پایان روز است، با یک کتابخانه تثبیتشده در پایتون بمانید. اتصالات پایتونی خودِ QuantLib انتخاب بالغتری هستند: همان موتور C++، گستردهترین پوشش ابزارها و سالها تجربه عملیاتی. سرعت بهندرت محدودیت اصلی در کدهای تحقیقاتی است؛ پوشش و صحت، اولویت دارند.
زمانی که حلقه محاسباتی داغ است و کدهای اطراف آن نیستند، یک موتور کامپایلشده را از پایتون فراخوانی کنید. هزینهای که باید مراقب آن باشید، مرز بین این دو است. فراخوانی به ازای هر قرارداد از پایتون، سربار (overhead) در هر عبور ایجاد میکند و راهحل، تحویل یک آرایه به موتور و دریافت یک آرایه از آن است. این همان جایگاهی است که itofin در کنار QuantLib-Python هدف قرار داده است.
زمانی Rust بنویسید که حلقه قیمتگذاری، خودِ محصول باشد: یک قیمتگذار درون یک سرویس قیمتدهی، یک اجرای ریسک طبق برنامهای که نمیتوانید از دست بدهید، یا یک فایل اجرایی که روی سیستمی بدون پایتون ارسال میشود. سوال دوم نیز به همینجا ختم میشود. نتایج ممیز شناور به ترتیب عملیات بستگی دارند، بنابراین یک مدل مشابه که دو بار پیادهسازی شده باشد میتواند در آخرین ارقام با هم اختلاف داشته باشد، و یک دفترچه تحقیقاتی که با سرویس تولیدی اختلاف دارد، یک هفته کارِ کارشناسی برای ریشهیابی میطلبد. استفاده از یک موتور واحد از هر دو سمت، این دسته از اختلافات را بهطور کامل حذف میکند، که استدلال پایداری برای داشتن یک هسته کامپایلشده با اتصالات (bindings) در هر زبانی است. همین غریزه در کارهای استراتژی نیز صدق میکند، همانطور که یک بکتست قابل بازتولید نشان میدهد.
هدف کالیبراسیون واقعاً کجاست
کالیبراسیون به چیزی برای برازش نیاز دارد و آن چیز، سطحی از نوسانپذیریهای ضمنی بازار است. بخش ریشهیابی آن در نحوه محاسبه نوسانپذیری ضمنی پوشش داده شده است. شکل زیر چیزی است که یک مدل باید با آن مطابقت داشته باشد.
کد دقیق 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)در نزدیکی قیمت (near the money)، قراردادهای AAPL در بازه 0 to 7 days بهطور میانگین دارای 29.6% نوسانپذیری ضمنی در جلسات مورد بررسی بودند، در حالی که این عدد برای 29.3% برابر با 241 days or more بود. مدلی که تنها یک پارامتر نوسانپذیری دارد نمیتواند همزمان روی هر دو نقطه بنشیند، و این دقیقاً دلیلی است که مدلهای دارای ساختار زمانی نوسانپذیری وجود دارند. برازش یک مدل بر سطحی مانند این، مرحله کالیبراسیون است و حساسیتهایی که از مدل برازششده استخراج میشوند، همان یونانیها (greeks) هستند که در توضیح یونانیهای اختیار معامله پوشش داده شدهاند.
نحوه ساخت این پنلها
پنل منحنی، آخرین تاریخ قیمتگذاریشده در سری خزانهداری را میخواند و یازده ستون تنور آن را به سطر تبدیل میکند و هر تنوری که در آن روز قیمتی نداشته باشد را حذف میکند. پنل جلسه، تاریخهای متمایز در نوار SPY را در هر ماه میشمارد که جایگزین مناسبی برای شمارش کامل جلسات معاملاتی است. پنل زنجیره، کدهای قرارداد متمایز با حجم غیرصفر در آخرین جلسه موجود را به تفکیک دارایی پایه میشمارد. پنل نوسانپذیری فقط حلهای همگرا با حجم غیرصفر و قیمتهای اعمالی (strikes) در محدوده 5 درصدی قیمت پایانی دارایی پایه را نگه میدارد که باند استاندارد «نزدیک به قیمت» است؛ بازه اول شامل سررسیدهای همان روز نیز میشود.
سوالات متداول
آیا libitofin جایگزین آماده برای QuantLib است؟
خیر. تا اوت 2026، این یک پورت پیش از نسخه 1.0 است که بخشی از سطح QuantLib را پوشش میدهد و نتایج خود را در برابر مجموعه تستهای خودِ QuantLib تایید میکند. خودِ QuantLib، که از طریق اتصالات پایتونی در دسترس است، همچنان گزینه گستردهتر و پایدارتر برای کارهای تولیدی است.
«پیش از نسخه 1.0» برای یک کتابخانه قیمتگذاری به چه معناست؟
طبق قرارداد نسخهگذاری Rust، یک نسخه 0.x هیچ وعده سازگاری نمیدهد: نسخه فرعی بعدی آزاد است هر چیزی را تغییر نام دهد یا حذف کند. در عمل، این یعنی قفل کردن روی یک نسخه تگشده دقیق و اجرای مجدد مجموعه تستهای خودتان در هر ارتقا.
آیا برای استفاده از libitofin نیاز به نوشتن Rust دارم؟
خیر. این پروژه یک بسته پایتون به نام itofin منتشر میکند، بنابراین موتور از یک فرآیند پایتون معمولی قابل فراخوانی است. نوشتن Rust زمانی اهمیت پیدا میکند که خودِ حلقه قیمتگذاری، چیزی باشد که شما در حال عرضه آن هستید.
یک کتابخانه قیمتگذاری چه چیزی به من میدهد که فرمول صفحهگسترده نمیدهد؟
منحنیها به جای نرخهای تکگانه، قراردادهای شمارش روز با تقویمهای معاملاتی واقعی در پشت آنها، یک حلقه کالیبراسیون که پارامترهای مدل را بر قیمتهای اعلامشده برازش میکند، و یک لایه عددی تستشده در زیر هر سه مورد. فرمولهای بسته (closed-form)، بخش کوچکی از کار هستند.
آیا زبان برنامهنویسی قیمت اختیار معامله را تغییر میدهد؟
از نظر ریاضی خیر. اما قابلیت بازتولید را تغییر میدهد: نتایج ممیز شناور به ترتیب عملیات بستگی دارند، بنابراین دو پیادهسازی از یک مدل میتوانند در ارقام نهایی اختلاف داشته باشند. اجرای تحقیق و تولید از یک موتور واحد، این شکاف را از بین میبرد.
هر پنل در اینجا دارای کد SQL دقیق خود در زیر است، بنابراین یکی را باز کنید تا ببینید شمارش چگونه انجام شده است. همان سوالات مربوط به منحنی، تقویم و زنجیره را میتوان به زبان ساده در ترمینال Strasmore پرسید.