Skip to content

quantbai/elvers

 
 

Repository files navigation

Elvers

PyPI CI License Python 3.10+ Polars

Polars-native factor computation engine for quantitative research. All operators execute as Rust-backed Polars expressions with no Python loops in the hot path.

Core Abstractions

  • Panel -- Balanced (timestamp, symbol) container with strict alignment guarantees. Prevents look-ahead bias by construction.
  • Factor -- Immutable signal vector. Every operator takes Factor and returns Factor with eager evaluation.

Numerical Conventions

Topic Convention
Missing values NaN and Inf are converted to null on Factor creation. The library operates on a single missing-value semantic (null only). Nulls propagate through all operations; boundary cases (constant window, insufficient data, zero denominator) return null explicitly.
Null in arithmetic Default: 5.0 + null = null. The add, subtract, multiply functions accept filter=True to treat null as the identity element (0 for +/-, 1 for *).
Division by zero All divisions guarded at abs(denominator) < 1e-10, returning null. Applies uniformly across divide, inverse, zscore, ts_zscore, ts_cv, ts_regression, and all neutralization operators.
Rank Range (0, 1]. Does not pass through zero. Ties use average method. Nulls excluded from ranking.
Standard deviation Population (ddof=0) for std, variance, zscore, normalize.
Correlation / Covariance Sample (ddof=1) for ts_corr, ts_covariance, ts_autocorr. Identity corr(x,y) = cov(x,y) / (std(x) * std(y)) holds.
Rolling warmup All ts_* operators require min_samples=window. First window-1 values per symbol are null.
ts_product Correctly handles negative values and zeros.

Installation

pip install elvers

Usage

from elvers import load, ts_rank, ts_regression, zscore, signal, group_neutralize panel = load("ohlcv.parquet") # or load() for built-in sample data close, volume = panel["close"], panel["volume"] momentum = ts_rank(close, 20) vol_adj = zscore(momentum) / zscore(ts_rank(volume, 20)) beta_resid = ts_regression(close, volume, window=60, rettype=0) alpha = signal(group_neutralize(vol_adj, panel["sector"]))

Sub-daily data is supported via the interval parameter:

panel = load("hourly.parquet", interval="1h")

Operators

70+ operators. All accept and return Factor.

Time-Series -- rolling window per symbol:

ts_delay ts_delta ts_mean ts_sum ts_std_dev ts_min ts_max ts_median ts_rank ts_skewness ts_kurtosis ts_zscore ts_corr ts_covariance ts_product ts_step ts_decay_linear ts_decay_exp_window days_from_last_change ts_av_diff ts_scale ts_percentile ts_quantile ts_cv ts_autocorr ts_count_nans ts_backfill kth_element last_diff_value inst_tvr ts_delta_limit ts_regression trade_when

Cross-Sectional -- across symbols at each timestamp:

rank zscore mean median scale normalize quantile signal winsorize truncate left_tail right_tail

Neutralization and Group -- sector/industry neutralization:

vector_neut regression_neut group_neutralize group_rank group_zscore group_scale group_normalize group_mean group_median group_backfill

Math:

log sqrt sign power signed_power inverse s_log_1p maximum minimum where

Arithmetic:

add subtract multiply divide reverse densify bucket and standard operators (+ - * / ** abs)

Development

pip install -e ".[dev]" pytest tests/ -v ruff check elvers/

See CLAUDE.md for full development standards.

About

High-performance multi-factor quantitative framework built on Polars.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages