Install toraniko via pip
mainInstall the toraniko library using pip. The project depends on numpy and polars.
pip install toranikorepository·main·Indexed 21 days ago
https://github.com/0xfdf/toranikoA multi-factor equity risk model for institutional-scale quantitative and systematic trading. Version 1.1.0 provides tools to create custom factors, estimate factor returns (market, sector, and style), and compute factor covariance matrices for portfolio optimization. It includes specialized implementations for momentum, size, and value factor scores, as well as mathematical utilities for cross-sectional normalization, winsorization, and exponential decay weighting using Polars and NumPy.
Install the toraniko library using pip. The project depends on numpy and polars.
pip install toranikoTo run a complete risk model estimation, you must provide three types of data in a format compatible with Polars/Pandas:
date (str), symbol (str), asset_returns (f64).date (str), symbol (str), book_price (f64), sales_price (f64), cf_price (f64), and market_cap (f64).Use toraniko.styles.factor_mom to estimate momentum factor scores from asset returns.
Parameters:
df: A Polars DataFrame containing at least symbol, date, and asset_returns.trailing_days: The lookback period for momentum calculation.winsor_factor: The factor used for winsorization.from toraniko.styles import factor_mom
mom_df = factor_mom(df.select("symbol", "date", "asset_returns"), trailing_days=252, winsor_factor=0.01).collect()Use toraniko.styles import factor_val to estimate value factor scores using price-to-book, price-to-sales, and price-to-cash-flow metrics.
Parameters:
df: A Polars DataFrame containing date, symbol, book_price, sales_price, and cf_price.from toraniko.styles import factor_val
value_df = factor_val(df.select("date", "symbol", "book_price", "sales_price", "cf_price")).collect()Use toraniko.utils.top_n_by_group to select the top $N$ assets within a group (e.g., top 3000 by market cap per date) to define your model universe.
Parameters:
lazy_df: A Polars LazyFrame.n: The number of assets to keep per group.group_by_col: The column to group by (e.g., "market_cap").grouping_keys: A tuple of columns to use for grouping (e.g., ("date",)).keep_group_col: Boolean indicating if the grouping column should be kept.from toraniko.utils import top_n_by_group
ddf = top_n_by_group(
ddf.lazy(),
3000,
"market_cap",
("date",),
True
).collect()Use toraniko.model.estimate_factor_returns to estimate factor returns (market, sector, and style) from your prepared datasets.
Parameters:
returns_df: DataFrame with date, symbol, and asset_returns.mkt_cap_df: DataFrame with date, symbol, and market_cap.sector_df: DataFrame containing sector scores (one row per asset, sector columns as 1/0).style_df: DataFrame containing estimated style factor scores (e.g., value, momentum, size).winsor_factor: Factor for winsorization (default is 0.1).residualize_styles: Boolean indicating whether to residualize styles.from toraniko.model import estimate_factor_returns
fac_df, eps_df = estimate_factor_returns(returns_df, mkt_cap_df, sector_df, style_df, winsor_factor=0.1, residualize_styles=False)Use factor_mom to estimate rolling symbol-by-symbol momentum factor scores based on asset returns. The function applies exponential weighting to returns over a trailing period and applies a lag to the observations.
Important: This function does not handle NaN or null values, nor does it account for pathological data distributions. You must ensure your input data is clean using the math and utils modules before calling this function.
Input Requirements:
returns_df must be a Polars DataFrame or LazyFrame containing the following columns:
datesymbolasset_returnsParameters:
returns_df: The input Polars DataFrame/LazyFrame.trailing_days (int, default 504): Look-back period for momentum measurement.half_life (int, default 126): Decay rate for exponential weighting (in days).lag (int, default 20): Number of days to lag the current day's return (e.g., 20 trading days for one month).winsor_factor (float, default 0.01): Percentile used for winsorization.Returns:
A Polars LazyFrame containing:
datesymbolmom_scoreimport polars as pl
from toraniko.styles import factor_mom
# Example usage with a Polars DataFrame
df = pl.DataFrame({
"date": ["2023-01-01", "2023-01-02"],
"symbol": ["AAPL", "AAPL"],
"asset_returns": [0.01, -0.005]
})
mom_scores = factor_mom(df, trailing_days=252, lag=20)Use norm_xsection() to rescale a column to a specific interval (defaulting to [0, 1]) based on the min/max values within a partition defined by over_col.
target_col: The column to normalize.over_col: The column used for partitioning (e.g., a date or group ID).lower: The lower bound of the rescaling interval.upper: The upper bound of the rescaling interval.Returns a Polars expression. NaN values are preserved and not used in min/max calculations.
# Rescale 'price' to [0, 100] within each 'sector'
df.with_columns(
norm_xsection(target_col='price', over_col='sector', lower=0, upper=100)
)The exp_weights() function generates an array of exponentially decaying weights for a given window size.
window: The number of points in the trailing lookback period (must be a positive integer).half_life: The decay rate (must be a positive integer).Returns a NumPy array of weights that decay by half every half_life indices.
import numpy as np
from toraniko.math import exp_weights
# Generate weights for a 10-day window with a 5-day half-life
weights = exp_weights(window=10, half_life=5)The smooth_features() function applies a rolling mean to specified columns to reduce noise.
Process:
sort_col.features using a trailing window of window_size.over_col to ensure smoothing stays within specific groups.Parameters:
df: A pl.DataFrame or pl.LazyFrame.features: A tuple[str, ...] of column names to smooth.sort_col: The column name used to sort the data (typically a timestamp or sequence).over_col: The column name used to partition the data (e.g., a user ID or sensor ID).window_size: An int representing the number of trailing periods for the moving average window.Returns:
A pl.LazyFrame where the features columns have been replaced by their moving average values.
Errors:
TypeError if df is not a Polars DataFrame or LazyFrame.ValueError if any of the features, sort_col, or over_col are missing from the input DataFrame.import polars as pl
from toraniko.utils import smooth_features
# Example usage
df = pl.DataFrame({
"id": [1, 1, 1, 1, 2, 2, 2, 2],
"time": [1, 2, 3, 4, 1, 2, 3, 4],
"val": [1.0, 2.0, 3.0, 4.0, 10.0, 20.0, 30.0, 40.0]
})
smoothed_lf = smooth_features(
df=df,
features=("val",),
sort_col="time",
over_col="id",
window_size=2
)The top_n_by_group() function identifies the highest-ranking rows within specific groups based on a ranking variable.
Parameters:
df: A pl.DataFrame or pl.LazyFrame.n: An int specifying how many top rows to select per group.rank_var: The column name used to determine the rank (descending order).group_var: A tuple[str, ...] of column names used to define the groups.filter: A bool (default True).True: Returns only the top n rows per group. The rank column is dropped.False: Returns all original rows, but adds a rank_mask column (1 if the row is in the top n, 0 otherwise).Returns:
A pl.LazyFrame containing the processed data.
Errors:
TypeError if df is not a Polars DataFrame or LazyFrame.ValueError if rank_var or any of the group_var columns are missing.import polars as pl
from toraniko.utils import top_n_by_group
# Example usage: Get top 2 values per group
df = pl.DataFrame({
"group": ["A", "A", "A", "B", "B", "B"],
"score": [10, 50, 30, 5, 100, 20]
})
# Returns only the top 2 rows per group
top_rows_lf = top_n_by_group(
df=df,
n=2,
rank_var="score",
group_var=("group",),
filter=True
)
# Returns all rows with a 'rank_mask' column
masked_lf = top_n_by_group(
df=df,
n=2,
rank_var="score",
group_var=("group",),
filter=False
)Use factor_sze to estimate rolling symbol-by-symbol size factor scores using market capitalization. The implementation follows the Fama-French SMB (Small Minus Big) logic, meaning it multiplies the log of market cap by -1 to capture the size risk premium associated with smaller firms.
Input Requirements:
mkt_cap_df must be a Polars DataFrame or LazyFrame containing:
datesymbolmarket_capParameters:
mkt_cap_df: The input Polars DataFrame/LazyFrame.lower_decile (float, default 0.2): Lower bound for percentile calculation.upper_decile (float, default 0.8): Upper bound for percentile calculation.Returns:
A Polars LazyFrame containing:
datesymbolsze_scoreimport polars as pl
from toraniko.styles import factor_sze
# Example usage
mkt_cap_df = pl.DataFrame({
"date": ["2023-01-01", "2023-01-01"],
"symbol": ["AAPL", "MSFT"],
"market_cap": [3e12, 2e12]
})
sze_scores = factor_sze(mkt_cap_df)