Skip to content

Commit 4e9d910

Browse files
committed
refactor: replace numpy ts_rank with native Polars rolling_rank; fix signal NaN semantics
- Replace ts_rank implementation: 35-line numpy stride_tricks with single-line Polars rolling_rank(method='min'), eliminating the last numpy dependency from the computation engine - Fix cross-sectional signal() NaN handling to match phandas semantics: null/insufficient data now yields 0.0 weights instead of propagating nulls across the entire cross-section - Unify polars version constraint in setup.py to >=1.35.0,<2.0.0 (was pinned to ==1.37.1), consistent with pyproject.toml - Remove quantile, ts_arg_max, ts_arg_min from public API exports; these operators rely on scipy/Python callbacks and are marked [DEV] pending pure Polars implementations - Add bundled sample dataset (elvers/data/crypto_1d.csv) for load() default usage
1 parent ef3daac commit 4e9d910

16 files changed

Lines changed: 6560 additions & 583 deletions

README.md

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,59 +4,86 @@
44

55
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
66
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
7-
[![Polars](https://img.shields.io/badge/Polars-1.37.1-green.svg)](https://pola.rs/)
7+
[![Polars](https://img.shields.io/badge/Polars-1.37+-CD853F.svg)](https://pola.rs/)
88

99
</div>
1010

11-
High-performance multi-factor quantitative framework built on Polars.
11+
**ELVERS** is a high-performance, strictly typed multi-factor alpha research engine powered by [Polars](https://pola.rs/).
1212

13-
Named after **ELVES**, the atmospheric lightning phenomenon that occurs at extreme speed.
1413

15-
## Features
14+
## Design Philosophy
15+
16+
Quantitative research requires rapid iteration over large universe panels without compromising execution speed. Legacy pandas-based pipelines are interpretable but inherently scale poorly. elvers addresses this through a robust two-layer abstraction:
17+
18+
- **`Panel`** — A continuous, balanced panel container enforcing strict `(timestamp, symbol)` alignment. It mitigates look-ahead bias and standardizes index integrity across all transformations.
19+
- **`Factor`** — A fully evaluated, eagerly executed vector of signal exposures. Bound directly to the global panel, factors resolve native Polars expressions instantaneously utilizing highly-parallelized core routines underneath Rust and C.
20+
21+
The architecture guarantees that complex computational graphs—from primitive time-series aggregations to complex cross-sectional neutralizations—are resolved at theoretical hardware peaks with virtually zero Python interpreter overhead in the hot path.
22+
1623

17-
- Pure Polars-based lazy evaluation
18-
- Expression-based factor computation
19-
- ~80x faster than pandas-based alternatives
20-
- Professional-grade operators (time-series, cross-sectional, neutralization)
2124

2225
## Installation
2326

2427
```bash
2528
pip install elvers
2629
```
2730

31+
32+
2833
## Quick Start
34+
Compose factors exactly as intuitively as they are expressed mathematically:
2935

3036
```python
31-
from elvers import load
32-
from elvers import ts_rank, rank, zscore, group_neutralize
37+
from elvers import load, ts_rank, zscore
3338

34-
# 1. Load data into a managed Panel
35-
panel = load("crypto_data.csv")
39+
# Load your own data
40+
# panel = load("your_ohlcv.csv")
41+
42+
# Load built-in sample dataset
43+
panel = load()
3644

37-
# 2. Access factors via panel indexing
3845
close = panel["close"]
3946
volume = panel["volume"]
4047

41-
# 3. Define factors using professional operators
48+
# Define and execute expressions instantly
4249
momentum = ts_rank(close, 20)
43-
alpha = zscore(rank(momentum))
44-
45-
# 4. Advanced: Neutralization
46-
# Neutralize alpha against volume groups
47-
final_alpha = group_neutralize(alpha, panel["group"])
50+
alpha = zscore(momentum)
4851

49-
# 5. Compute results
50-
result = panel.collect(final_alpha)
52+
# Extract native Polars DataFrame
53+
result = alpha.df
5154
print(result)
5255
```
5356

54-
## License
57+
Both `Panel` and `Factor` expose a `.df` property that returns the underlying `pl.DataFrame`:
58+
59+
- **`panel.df`** — Full panel frame with all OHLCV columns intact.
60+
- **`factor.df`** — Flat `(T_days * N_symbols, 3)` frame aligned to the original spatial coordinates:
61+
62+
```text
63+
shape: (T_days * N_symbols, 3)
64+
┌────────────┬────────┬───────────┐
65+
│ timestamp ┆ symbol ┆ factor │
66+
│ --- ┆ --- ┆ --- │
67+
│ date ┆ str ┆ f64 │
68+
╞════════════╪════════╪═══════════╡
69+
│ 2024-01-01 ┆ BTC ┆ null │
70+
│ ... ┆ ... ┆ ... │
71+
│ 2024-12-31 ┆ ETH ┆ 1.243 │
72+
└────────────┴────────┴───────────┘
73+
```
74+
75+
Rows are ordered by `timestamp` (ascending), then `symbol` (ascending).
76+
77+
> **Note**: Rolling window operators naturally yield `null` for the initial `window - 1` periods per symbol. The full dense panel shape is preserved throughout all operations.
5578
56-
MIT License - see [LICENSE](LICENSE) for details.
5779

58-
## Author
80+
## Operator Library
5981

60-
quantbai
82+
| Category | Supported Operators |
83+
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
84+
| **Time-Series** | `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_decay_linear`, `ts_av_diff`, `ts_scale`, `ts_quantile`, `ts_cv`, `ts_autocorr`, `ts_count_nans`, `ts_backfill` |
85+
| **Cross-Sectional** | `rank`, `zscore`, `mean`, `median`, `scale`, `normalize`, `signal` |
86+
| **Neutralization** | `vector_neut`, `regression_neut`, `group_neutralize`, `group_rank`, `group_zscore`, `group_scale`, `group_normalize` |
87+
| **Math** | `log`, `ln`, `sqrt`, `sign`, `power`, `signed_power`, `inverse`, `s_log_1p`, `maximum`, `minimum`, `where`, standard operators (`+`, `-`, `*`, `/`, `**`, `abs`) |
6188

6289

elvers/__init__.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@
1111
__author__ = "quantbai"
1212

1313
from .core import Factor
14-
from .io import load, Panel, PanelInfo
14+
from .io import load, Panel
1515

1616
from .ops import (
1717
add, subtract, multiply, divide, reverse,
1818

1919
ts_delay, ts_delta, ts_mean, ts_sum, ts_std_dev,
2020
ts_min, ts_max, ts_median, ts_rank, ts_skewness, ts_kurtosis,
2121
ts_zscore, ts_corr, ts_covariance, ts_product,
22-
ts_arg_max, ts_arg_min, ts_decay_linear, ts_av_diff, ts_scale,
22+
ts_decay_linear, ts_av_diff, ts_scale,
2323
ts_quantile, ts_cv, ts_autocorr,
2424
ts_count_nans, ts_backfill,
2525

2626
rank, zscore, mean, median,
27-
scale, normalize, quantile, signal,
27+
scale, normalize, signal,
2828

2929
log, ln, sqrt, sign, power, signed_power,
3030
inverse, s_log_1p, maximum, minimum, where,
@@ -38,19 +38,19 @@
3838
"__version__",
3939
"__author__",
4040
"Factor",
41-
"load", "Panel", "PanelInfo",
41+
"load", "Panel",
4242

4343
"add", "subtract", "multiply", "divide", "reverse",
4444

4545
"ts_delay", "ts_delta", "ts_mean", "ts_sum", "ts_std_dev",
4646
"ts_min", "ts_max", "ts_median", "ts_rank", "ts_skewness", "ts_kurtosis",
4747
"ts_zscore", "ts_corr", "ts_covariance", "ts_product",
48-
"ts_arg_max", "ts_arg_min", "ts_decay_linear", "ts_av_diff", "ts_scale",
48+
"ts_decay_linear", "ts_av_diff", "ts_scale",
4949
"ts_quantile", "ts_cv", "ts_autocorr",
5050
"ts_count_nans", "ts_backfill",
5151

5252
"rank", "zscore", "mean", "median",
53-
"scale", "normalize", "quantile", "signal",
53+
"scale", "normalize", "signal",
5454

5555
"log", "ln", "sqrt", "sign", "power", "signed_power",
5656
"inverse", "s_log_1p", "maximum", "minimum", "where",

0 commit comments

Comments
 (0)