مدل‌سازی مالی با دیتافریم‌های Oxtapus#

این راهنما برای کسیه که مفاهیم مالی رو می‌شناسه و می‌خواد با چند خط پایتون دادهٔ تمیز آمادهٔ مدل بسازه. مثال‌ها با Polars نوشته شدن؛ همهٔ محاسبه‌ها محلی هستن و بعد از دریافت داده به سایت منبع درخواست تازه‌ای نمی‌زنن.

بازده ساده و لگاریتمی#

import polars as pl
import oxtapus as ox

prices = ox.tsetmc.daily_prices(
    ["فولاد", "فملی", "خودرو"],
    start="1402/10/11",
    end="1404/10/08",
)

returns = prices.sort(["symbol", "trading_date"]).with_columns(
    simple_return=(pl.col("close_price") / pl.col("close_price").shift(1) - 1).over("symbol"),
    log_return=(pl.col("close_price").log() - pl.col("close_price").shift(1).log()).over("symbol"),
)

ردیف اول هر نماد بازده نداره و مقدارش null می‌مونه؛ Oxtapus و Polars اون رو به صفر تبدیل نمی‌کنن.

Rolling Volatility#

نمونهٔ زیر برای هر نماد، انحراف معیار بازده لگاریتمی رو از ۳۰ بازده ثبت‌شدهٔ اخیر محاسبه می‌کنه و بعد با فرض ۲۴۰ روز معاملاتی، نوسان‌پذیری سالانه‌شده رو به دست میاره:

risk = returns.with_columns(
    volatility_30d=pl.col("log_return").rolling_std(window_size=30).over("symbol"),
).with_columns(
    annualized_volatility=pl.col("volatility_30d") * (240**0.5),
)

این سالانه‌سازی از قاعدهٔ ریشهٔ دوم زمان استفاده می‌کنه. تعداد مشاهده‌ها و تعداد روزهای معاملاتی سال رو باید متناسب با بازار و فرض‌های مدل انتخاب کنی.

ساخت ماتریس بازده برای تحلیل همبستگی یا رگرسیون#

return_matrix = (
    returns.select("trading_date", "symbol", "simple_return")
    .pivot(
        index="trading_date",
        on="symbol",
        values="simple_return",
    )
    .sort("trading_date")
)

هر ستون حالا بازده یک نماده. قبل از برآورد همبستگی یا برازش مدل، روش برخورد با روزهای بدون معامله و مقدارهای null رو آگاهانه مشخص کن؛ جایگزینی خودکار اون‌ها با صفر معمولاً یک فرض مالی نادرست وارد مدل می‌کنه.

هم‌ترازسازی بازده سهم و دلار#

usd = (
    ox.tgju.daily_prices("دلار", start="1402/10/11")
    .sort("trading_date")
    .with_columns(
        usd_return=pl.col("close_price") / pl.col("close_price").shift(1) - 1,
    )
    .select("trading_date", "usd_return")
)

foolad = returns.filter(pl.col("symbol") == "فولاد").select("trading_date", "simple_return")

model_frame = foolad.join(usd, on="trading_date", how="inner")

اتصال داخلی (inner join) فقط تاریخ‌های مشترک رو نگه می‌داره. برای مقایسهٔ بازارهایی با تقویم معاملاتی متفاوت، این انتخاب معمولاً شفاف‌تر از پرکردن خودکار روزهای بدون مشاهده است.

نقدشوندگی و اختلاف بهترین قیمت خرید و فروش (Bid–Ask Spread)#

book = ox.tsetmc.market_depth("فولاد")

top = (
    book.filter(pl.col("order_book_level") == 1)
    .with_columns(
        spread=pl.col("ask_price") - pl.col("bid_price"),
        mid_price=(pl.col("ask_price") + pl.col("bid_price")) / 2,
    )
    .with_columns(
        relative_spread=pl.col("spread") / pl.col("mid_price"),
    )
)

این داده فقط وضعیت دفتر سفارش‌ها در لحظهٔ دریافت رو نشون می‌ده، نه تاریخچهٔ اون رو. برای پژوهش درون‌روزی باید وضعیت دفتر سفارش‌ها رو در زمان‌های مشخص ذخیره کنی و محدودیت نرخ درخواست منبع رو رعایت کنی.

کنترل کیفیت قبل از مدل#

from oxtapus import Client

with Client() as client:
    result = client.market.fetch_daily_prices(["فولاد", "خودرو"])

assert result.quality_summary["passed"]
print(result.failures)
print(result.warnings)

برای کار پژوهشی بهتره بازهٔ زمانی، نسخهٔ بسته، زمان دریافت، پارامترهای درخواست، نسخهٔ طرح‌واره و هش یا نسخهٔ فایل ورودی مدل رو کنار نتیجه نگه داری تا تحلیل بعداً قابل‌بازتولید باشه.