Skip to content

Commit e992249

Browse files
committed
feat: Complete factor engine with 80+ operators
- Core: Factor class, context management, lazy evaluation - Time-series: ts_mean, ts_rank, ts_corr, ts_skewness, ts_kurtosis, etc. - Cross-sectional: rank, zscore, scale, normalize, quantile, etc. - Math: log, sqrt, power, clip, where, etc. - Neutralization: vector_neut, regression_neut, group_demean, winsorize, etc. - ~80x faster than pandas-based implementation - MIT License
1 parent 0fe820f commit e992249

12 files changed

Lines changed: 1159 additions & 11 deletions

File tree

README.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,47 @@
11
# Elvers
22

3+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4+
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
5+
[![Polars](https://img.shields.io/badge/Polars-1.35+-green.svg)](https://pola.rs/)
6+
37
High-performance multi-factor quantitative framework built on Polars.
48

59
Named after **ELVES** (Emission of Light and Very Low Frequency perturbations due to Electromagnetic Pulse Sources), the atmospheric lightning phenomenon that occurs at extreme speed.
610

7-
## Features (Coming Soon)
11+
## Features
812

913
- Pure Polars-based lazy evaluation
1014
- Expression-based factor computation
11-
- Lightning-fast backtesting engine
12-
- Professional-grade IC/ICIR analysis
15+
- ~80x faster than pandas-based alternatives
16+
- Professional-grade operators (time-series, cross-sectional, neutralization)
1317

1418
## Installation
1519

1620
```bash
1721
pip install elvers
1822
```
1923

24+
## Quick Start
25+
26+
```python
27+
from elvers import load, col, collect_all
28+
from elvers import ts_mean, ts_rank, rank, zscore
29+
30+
load("data.csv")
31+
32+
close = col("close")
33+
momentum = ts_rank(close, 20)
34+
alpha = zscore(rank(momentum))
35+
36+
result = collect_all(alpha)
37+
```
38+
39+
## License
40+
41+
MIT License - see [LICENSE](LICENSE) for details.
42+
2043
## Author
2144

2245
Phantom Management
46+
47+

elvers/__init__.py

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,75 @@
11
"""
2-
Elvers - High-performance multi-factor quantitative framework.
2+
Elvers - Polars-native factor expression engine.
33
4-
Built on Polars for lightning-fast factor research and backtesting.
5-
Named after ELVES (Emission of Light and Very Low Frequency perturbations),
6-
the atmospheric lightning phenomenon.
4+
Ultra-minimal, maximum performance WQ-style factor expressions.
5+
Built on Polars for lightning-fast factor research.
76
87
Author: Phantom Management
98
"""
109

11-
__version__ = "0.0.1"
10+
__version__ = "0.1.0"
1211
__author__ = "Phantom Management"
1312

13+
from .core import (
14+
Factor,
15+
load,
16+
lf,
17+
col,
18+
collect,
19+
collect_all,
20+
to_csv,
21+
to_pandas,
22+
)
23+
24+
from .ops import (
25+
add, sub, mul, div, reverse,
26+
27+
ts_delay, ts_delta, ts_mean, ts_sum, ts_std_dev,
28+
ts_min, ts_max, ts_median, ts_rank, ts_skewness, ts_kurtosis,
29+
ts_zscore, ts_corr, ts_covariance, ts_product,
30+
ts_arg_max, ts_arg_min, ts_decay_linear, ts_av_diff, ts_scale,
31+
ts_var, ts_quantile_val, ts_cv, ts_autocorr,
32+
ts_return, ts_log_return, ts_pct_change,
33+
ts_count_nulls, ts_step,
34+
ts_ewm_mean, ts_ewm_std, ts_ewm_var,
35+
36+
rank, zscore, mean, median, sum_, std,
37+
scale, normalize, quantile, spread, signal,
38+
39+
log, ln, sqrt, abs_, sign, power, signed_power,
40+
inverse, s_log_1p, maximum, minimum, where,
41+
clip, nan_to_value, fill_null,
42+
43+
vector_neut, regression_neut, demean, market_neut,
44+
group_demean, group_rank, group_zscore, group_scale,
45+
group_normalize, winsorize, mad_winsorize,
46+
)
47+
48+
__all__ = [
49+
"__version__",
50+
"__author__",
51+
"Factor",
52+
"load", "lf", "col", "collect", "collect_all", "to_csv", "to_pandas",
53+
54+
"add", "sub", "mul", "div", "reverse",
55+
56+
"ts_delay", "ts_delta", "ts_mean", "ts_sum", "ts_std_dev",
57+
"ts_min", "ts_max", "ts_median", "ts_rank", "ts_skewness", "ts_kurtosis",
58+
"ts_zscore", "ts_corr", "ts_covariance", "ts_product",
59+
"ts_arg_max", "ts_arg_min", "ts_decay_linear", "ts_av_diff", "ts_scale",
60+
"ts_var", "ts_quantile_val", "ts_cv", "ts_autocorr",
61+
"ts_return", "ts_log_return", "ts_pct_change",
62+
"ts_count_nulls", "ts_step",
63+
"ts_ewm_mean", "ts_ewm_std", "ts_ewm_var",
64+
65+
"rank", "zscore", "mean", "median", "sum_", "std",
66+
"scale", "normalize", "quantile", "spread", "signal",
67+
68+
"log", "ln", "sqrt", "abs_", "sign", "power", "signed_power",
69+
"inverse", "s_log_1p", "maximum", "minimum", "where",
70+
"clip", "nan_to_value", "fill_null",
71+
72+
"vector_neut", "regression_neut", "demean", "market_neut",
73+
"group_demean", "group_rank", "group_zscore", "group_scale",
74+
"group_normalize", "winsorize", "mad_winsorize",
75+
]

elvers/core/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""Core module exports."""
2+
3+
from .factor import Factor
4+
from .context import load, lf, col, collect, collect_all, to_csv, to_pandas
5+
6+
__all__ = [
7+
"Factor",
8+
"load",
9+
"lf",
10+
"col",
11+
"collect",
12+
"collect_all",
13+
"to_csv",
14+
"to_pandas",
15+
]

elvers/core/context.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"""Global LazyFrame context management."""
2+
3+
from __future__ import annotations
4+
import polars as pl
5+
from typing import Union
6+
from pathlib import Path
7+
8+
from .factor import Factor
9+
10+
11+
_LF: pl.LazyFrame | None = None
12+
13+
14+
def load(source: Union[str, Path, pl.LazyFrame, pl.DataFrame]) -> pl.LazyFrame:
15+
"""Load data and set as global LazyFrame context.
16+
17+
Data is automatically sorted by (symbol, timestamp) to ensure
18+
correct per-symbol rolling operations.
19+
20+
Parameters
21+
----------
22+
source : str, Path, LazyFrame, or DataFrame
23+
CSV path, Parquet path, or existing Polars object
24+
25+
Returns
26+
-------
27+
pl.LazyFrame
28+
The loaded LazyFrame (also set as global context)
29+
"""
30+
global _LF
31+
32+
if isinstance(source, pl.LazyFrame):
33+
_LF = source.sort(["symbol", "timestamp"])
34+
elif isinstance(source, pl.DataFrame):
35+
_LF = source.lazy().sort(["symbol", "timestamp"])
36+
elif isinstance(source, (str, Path)):
37+
path = str(source)
38+
if path.endswith(".parquet"):
39+
_LF = pl.scan_parquet(path).sort(["symbol", "timestamp"])
40+
else:
41+
_LF = pl.scan_csv(path).sort(["symbol", "timestamp"])
42+
else:
43+
raise TypeError(f"Unsupported source type: {type(source)}")
44+
45+
return _LF
46+
47+
48+
def lf() -> pl.LazyFrame:
49+
"""Get the current global LazyFrame context."""
50+
if _LF is None:
51+
raise RuntimeError("No data loaded. Call load() first.")
52+
return _LF
53+
54+
55+
def col(name: str) -> Factor:
56+
"""Create a Factor from a column name.
57+
58+
This is the primary entry point for accessing data columns.
59+
60+
Parameters
61+
----------
62+
name : str
63+
Column name in the loaded data
64+
65+
Returns
66+
-------
67+
Factor
68+
Factor wrapping pl.col(name)
69+
"""
70+
return Factor(pl.col(name), name)
71+
72+
73+
def collect(*factors: Factor) -> pl.DataFrame:
74+
"""Execute computation and return DataFrame with factor columns.
75+
76+
Parameters
77+
----------
78+
*factors : Factor
79+
Factors to compute
80+
81+
Returns
82+
-------
83+
pl.DataFrame
84+
DataFrame with timestamp, symbol, and factor columns
85+
"""
86+
if _LF is None:
87+
raise RuntimeError("No data loaded. Call load() first.")
88+
89+
base_cols = [pl.col("timestamp"), pl.col("symbol")]
90+
factor_cols = [f.expr.alias(f.name) for f in factors]
91+
92+
return _LF.select(base_cols + factor_cols).collect()
93+
94+
95+
def collect_all(*factors: Factor) -> pl.DataFrame:
96+
"""Execute computation and return DataFrame with all original columns plus factors.
97+
98+
Parameters
99+
----------
100+
*factors : Factor
101+
Factors to compute
102+
103+
Returns
104+
-------
105+
pl.DataFrame
106+
DataFrame with all original columns plus factor columns
107+
"""
108+
if _LF is None:
109+
raise RuntimeError("No data loaded. Call load() first.")
110+
111+
factor_cols = [f.expr.alias(f.name) for f in factors]
112+
113+
return _LF.with_columns(factor_cols).collect()
114+
115+
116+
def to_csv(*factors: Factor, path: str) -> None:
117+
"""Compute factors and write to CSV."""
118+
collect(*factors).write_csv(path)
119+
120+
121+
def to_pandas(*factors: Factor):
122+
"""Compute factors and convert to pandas DataFrame."""
123+
return collect(*factors).to_pandas()

0 commit comments

Comments
 (0)