Skip to content

Commit ed68fdc

Browse files
quantbaiclaude
andcommitted
docs: tighten README, remove redundancy
- Merged three overlapping null-handling entries into two - Removed implementation details from ts_product convention - Fixed "compile to" -> "execute as" (more precise) - Removed redundant code comments and output block from Usage - Operators listed with space-separated formatting for readability Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 15b36d6 commit ed68fdc

1 file changed

Lines changed: 23 additions & 37 deletions

File tree

README.md

Lines changed: 23 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,25 @@
1010

1111
</div>
1212

13-
Polars-native factor computation engine for quantitative research.
14-
15-
All operators compile to Rust-backed Polars expressions -- no Python loops in the hot path.
13+
Polars-native factor computation engine for quantitative research. All operators execute as Rust-backed Polars expressions with no Python loops in the hot path.
1614

1715
## Core Abstractions
1816

1917
- **`Panel`** -- Balanced `(timestamp, symbol)` container with strict alignment guarantees. Prevents look-ahead bias by construction.
20-
- **`Factor`** -- Immutable signal vector. Every operator takes `Factor` and returns `Factor` with eager evaluation. Null propagation follows explicit rules; all division operations are zero-guarded.
18+
- **`Factor`** -- Immutable signal vector. Every operator takes `Factor` and returns `Factor` with eager evaluation.
2119

2220
## Numerical Conventions
2321

2422
| Topic | Convention |
2523
| --- | --- |
26-
| **Rank range** | `(0, 1]` -- minimum is `1/n`, maximum is `1.0`. Does not pass through zero. Ties use `average` method. Null values are excluded from ranking, not assigned a rank. |
27-
| **Standard deviation** | Population (ddof=0) for all `std`, `variance`, `zscore`, `normalize` operators. |
28-
| **Correlation / Covariance** | Sample (ddof=1) for `ts_corr`, `ts_covariance`, `ts_autocorr`. Consistent: `corr(x,y) = cov(x,y) / (std(x) * std(y))` holds when using the same ddof. |
29-
| **Rolling warmup** | All `ts_*` operators use `min_samples=window`. The first `window-1` values per symbol are null. |
30-
| **Division by zero** | All divisions are guarded at `abs(denominator) < 1e-10`, returning null. This applies to `divide`, `inverse`, `zscore`, `ts_zscore`, `ts_cv`, `ts_regression`, and all neutralization operators. |
31-
| **Negative products** | `ts_product` handles negative values via sign-magnitude decomposition: counts negatives in window for sign, computes `exp(sum(log(abs(x))))` for magnitude. Zero in window produces zero. |
32-
| **Null in arithmetic** | Default: null propagates (`5.0 + null = null`). The `add`, `subtract`, `multiply` operators accept `filter=True` to treat null as the identity element (0 for add/subtract, 1 for multiply), so `add(a, b, filter=True)` yields `5.0 + null = 5.0`. Direct `+`/`-`/`*` operators always propagate null. |
33-
| **Null propagation** | Nulls propagate naturally through all operations. Boundary cases (constant window, insufficient data, zero denominator) return null explicitly -- never through implicit Inf-to-null conversion. |
34-
| **NaN / Inf / null** | Polars distinguishes NaN (IEEE 754) from null (missing). Elvers unifies them: the Factor constructor converts both NaN and Inf to null on creation. The entire library operates on a single missing-value semantic (null only), eliminating NaN-propagation bugs. No operator produces NaN or Inf as a valid result. |
24+
| **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. |
25+
| **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 *). |
26+
| **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. |
27+
| **Rank** | Range `(0, 1]`. Does not pass through zero. Ties use `average` method. Nulls excluded from ranking. |
28+
| **Standard deviation** | Population (ddof=0) for `std`, `variance`, `zscore`, `normalize`. |
29+
| **Correlation / Covariance** | Sample (ddof=1) for `ts_corr`, `ts_covariance`, `ts_autocorr`. Identity `corr(x,y) = cov(x,y) / (std(x) * std(y))` holds. |
30+
| **Rolling warmup** | All `ts_*` operators require `min_samples=window`. First `window-1` values per symbol are null. |
31+
| **ts_product** | Correctly handles negative values and zeros. |
3532

3633
## Installation
3734

@@ -47,26 +44,10 @@ from elvers import load, ts_rank, ts_regression, zscore, signal, group_neutraliz
4744
panel = load("ohlcv.parquet") # or load() for built-in sample data
4845
close, volume = panel["close"], panel["volume"]
4946

50-
# Compose arbitrarily -- each expression evaluates immediately
5147
momentum = ts_rank(close, 20)
5248
vol_adj = zscore(momentum) / zscore(ts_rank(volume, 20))
5349
beta_resid = ts_regression(close, volume, window=60, rettype=0)
5450
alpha = signal(group_neutralize(vol_adj, panel["sector"]))
55-
56-
print(alpha.df)
57-
```
58-
59-
```text
60-
shape: (T * N, 3)
61-
+------------+--------+-----------+
62-
| timestamp | symbol | factor |
63-
| --- | --- | --- |
64-
| date | str | f64 |
65-
+============+========+===========+
66-
| 2024-01-01 | BTC | -0.167 |
67-
| 2024-01-01 | ETH | 0.333 |
68-
| ... | ... | ... |
69-
+------------+--------+-----------+
7051
```
7152

7253
Sub-daily data is supported via the `interval` parameter:
@@ -77,22 +58,27 @@ panel = load("hourly.parquet", interval="1h")
7758

7859
## Operators
7960

80-
70+ operators across five categories. All return `Factor`.
61+
70+ operators. All accept and return `Factor`.
62+
63+
**Time-Series** -- rolling window per symbol:
8164

82-
**Time-Series** -- rolling window per symbol (`min_samples=window`, population std ddof=0, sample corr/cov ddof=1):
83-
`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`
65+
`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`
8466

8567
**Cross-Sectional** -- across symbols at each timestamp:
86-
`rank`, `zscore`, `mean`, `median`, `scale`, `normalize`, `quantile`, `signal`, `winsorize`, `truncate`, `left_tail`, `right_tail`
8768

88-
**Neutralization and Group** -- factor-driven grouping for sector/industry neutralization:
89-
`vector_neut`, `regression_neut`, `group_neutralize`, `group_rank`, `group_zscore`, `group_scale`, `group_normalize`, `group_mean`, `group_median`, `group_backfill`
69+
`rank` `zscore` `mean` `median` `scale` `normalize` `quantile` `signal` `winsorize` `truncate` `left_tail` `right_tail`
70+
71+
**Neutralization and Group** -- sector/industry neutralization:
72+
73+
`vector_neut` `regression_neut` `group_neutralize` `group_rank` `group_zscore` `group_scale` `group_normalize` `group_mean` `group_median` `group_backfill`
9074

9175
**Math**:
92-
`log`, `sqrt`, `sign`, `power`, `signed_power`, `inverse`, `s_log_1p`, `maximum`, `minimum`, `where`
76+
77+
`log` `sqrt` `sign` `power` `signed_power` `inverse` `s_log_1p` `maximum` `minimum` `where`
9378

9479
**Arithmetic**:
95-
`add`, `subtract`, `multiply`, `divide`, `reverse`, `densify`, `bucket`, and standard operators (`+`, `-`, `*`, `/`, `**`, `abs`)
80+
81+
`add` `subtract` `multiply` `divide` `reverse` `densify` `bucket` and standard operators (`+` `-` `*` `/` `**` `abs`)
9682

9783
## Development
9884

0 commit comments

Comments
 (0)