Skip to content

Commit 2e0a707

Browse files
authored
Merge pull request #3 from quantbai/dev
release: v0.3.0
2 parents 3f9d349 + badd468 commit 2e0a707

7 files changed

Lines changed: 499 additions & 115 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ Numerical changes are marked with [NUMERICAL].
77

88
## [Unreleased]
99

10+
## [0.3.0] - 2026-03-24
11+
12+
### Fixed
13+
- [NUMERICAL] `ts_covariance`: unified to ddof=0 (population), consistent with all other
14+
variance/std operators. Fixes the broken identity `cov/(std_x*std_y) == corr` which
15+
previously had ~5-20% error due to mixed ddof. Cross-validated against numpy (diff < 1e-15).
16+
17+
### Changed
18+
- OPERATORS.md rewritten as pure operator reference manual (signatures, behavior, edge cases)
19+
- Design rationale moved to CLAUDE.md Section 4.1 (developer-facing)
20+
- Fixed incorrect signatures in docs: `trade_when`, `scale`, `bucket`
21+
- Fixed README example code to use only columns present in sample data
22+
1023
## [0.2.0] - 2026-03-23
1124

1225
### Added
@@ -24,7 +37,7 @@ Numerical changes are marked with [NUMERICAL].
2437

2538
### Fixed
2639
- [NUMERICAL] `ts_product`: silently returned null for negative inputs; now correctly handles negative values via sign-magnitude decomposition
27-
- [NUMERICAL] `ts_covariance`: used ddof=0 (population) inconsistent with `ts_corr` (ddof=1); aligned to ddof=1 (sample)
40+
- [NUMERICAL] `ts_covariance`: added explicit ddof parameter (was using Polars default)
2841
- [NUMERICAL] `divide()`: no zero-denominator protection; now returns null where abs(divisor) < 1e-10
2942
- [NUMERICAL] `inverse()`: no zero protection; now returns null where abs(x) < 1e-10
3043
- `ts_regression` rettype=7 (MSE): implicit Inf-to-null on window=2; now has explicit guard

CLAUDE.md

Lines changed: 47 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ elvers/
3636
tests/
3737
conftest.py Test fixtures (make_ts, make_factor)
3838
test_*.py One test file per operator module
39+
OPERATORS.md Operator specification: numerical conventions, per-operator behavior, design rationale
40+
CLAUDE.md Development standards (this file)
3941
```
4042

4143
---
@@ -92,13 +94,34 @@ git push -u origin fix/bug-name
9294

9395
### 4.1 Numerical Correctness (Highest Priority)
9496

97+
Operator behavior reference: [OPERATORS.md](OPERATORS.md).
98+
The rules below are for writing new code:
99+
95100
- All divisions MUST have explicit zero guards:
96101
`pl.when(denom.abs() < 1e-10).then(None).otherwise(num / denom)`
97102
- NEVER rely on the Factor constructor's implicit Inf-to-null conversion as normal logic flow
98-
- Statistical convention: ddof=0 (population) for std/variance, ddof=1 (sample) for corr/cov.
99-
This is consistent across the entire library.
100103
- Null semantics: null propagates naturally through Polars expressions. Boundary cases
101-
(zero denominator, constant window, insufficient data) must be handled explicitly.
104+
(zero denominator, constant window, insufficient data) must be handled explicitly
105+
106+
#### Design Decisions (rationale for current conventions)
107+
108+
- **NaN/Inf unified to null**: eliminates the NaN-infection problem (`NaN + 1 = NaN`)
109+
that silently corrupts downstream computations. The Factor constructor converts on
110+
creation so the entire library operates on a single missing-value type.
111+
- **ddof=0 everywhere**: rolling windows and cross-sections operate on the full observed
112+
population, not a sample from a larger one. ddof=0 is semantically correct and avoids
113+
n=1 division-by-zero (ddof=1 divides by n-1=0).
114+
- **ts_corr/ts_autocorr use ddof=1 internally**: Polars `rolling_corr(ddof=0)` has a bug
115+
where ddof only applies to the covariance numerator, not the variance denominator,
116+
producing values outside [-1, 1]. Reported: https://github.com/pola-rs/polars/issues/16161.
117+
Correlation is ddof-invariant (cancels in ratio), so ddof=1 output is correct.
118+
- **rank range (0, 1] not [0, 1]**: a rank of 0 is ambiguous (could mean "missing" or
119+
"lowest"). Strictly positive range ensures every ranked value is distinguishable from null.
120+
- **Zero guard threshold 1e-10**: conservative enough to catch near-zero denominators,
121+
small enough not to interfere with legitimate small values in financial data.
122+
- **ts_product sign-magnitude decomposition**: naive `exp(sum(log(x)))` fails for negative
123+
inputs because `log(x)` is undefined for x < 0. Separating sign and magnitude handles
124+
this correctly.
102125

103126
### 4.2 Operator Writing Rules
104127

@@ -178,7 +201,8 @@ negative values. Previously returned null, now returns correct product.
178201

179202
## 7. Code Review Rules
180203

181-
- All PRs require at least one reviewer approval before merge
204+
- When the team has multiple developers, enable "Require approvals" in branch protection
205+
- Currently (single-developer mode): CI status checks are required, review approval is optional
182206
- Reviewer must verify:
183207
1. Tests pass and cover the change
184208
2. Numerical correctness (manually verify at least one expected value)
@@ -220,26 +244,26 @@ pytest tests/ -v
220244
ruff check elvers/
221245

222246
# 2. Update version number (single source of truth)
223-
# Edit elvers/__init__.py: __version__ = "0.2.0"
247+
# Edit elvers/__init__.py: __version__ = "X.Y.Z"
224248

225249
# 3. Update CHANGELOG.md
226-
# Move items from [Unreleased] to [0.2.0] - YYYY-MM-DD
250+
# Move items from [Unreleased] to [X.Y.Z] - YYYY-MM-DD
227251

228252
# 4. Commit the release
229253
git add elvers/__init__.py CHANGELOG.md
230-
git commit -m "release: v0.2.0"
254+
git commit -m "release: vX.Y.Z"
231255
git push origin dev
232256

233257
# 5. Create PR: dev -> main on GitHub
234-
# Title: "release: v0.2.0"
258+
# Title: "release: vX.Y.Z"
235259
# Wait for CI to pass and review approval
236260
# Squash merge on GitHub
237261

238262
# 6. Tag on main (after PR merged)
239263
git checkout main
240264
git pull origin main
241-
git tag v0.2.0
242-
git push origin v0.2.0
265+
git tag vX.Y.Z
266+
git push origin vX.Y.Z
243267

244268
# 7. Automated (triggered by tag push):
245269
# - CI runs full test suite again
@@ -251,14 +275,14 @@ git push origin v0.2.0
251275

252276
### What Happens Automatically
253277

254-
When you push a tag like `v0.2.0`:
278+
When you push a tag like `vX.Y.Z`:
255279

256280
1. `.github/workflows/publish.yml` triggers
257281
2. Runs full test suite on Python 3.10-3.13 (safety net)
258282
3. If tests pass: builds package, publishes to PyPI
259283
4. Creates a GitHub Release page at github.com/quantbai/elvers/releases
260284
with auto-generated release notes from commit messages
261-
5. Users can now `pip install elvers==0.2.0`
285+
5. Users can now `pip install elvers==X.Y.Z # specific version`
262286

263287
### What You See on GitHub After Release
264288

@@ -268,29 +292,15 @@ When you push a tag like `v0.2.0`:
268292

269293
---
270294

271-
## 10. One-Time Setup (for repository admin)
272-
273-
### PyPI Trusted Publisher (required for automated publishing)
274-
275-
1. Go to https://pypi.org -> Your projects -> elvers -> Publishing
276-
2. Add a new publisher:
277-
- Owner: quantbai
278-
- Repository: elvers
279-
- Workflow name: publish.yml
280-
- Environment: (leave blank)
295+
## 10. Setup
281296

282-
### GitHub Branch Protection (strongly recommended)
297+
### Infrastructure (already configured)
283298

284-
1. GitHub repo -> Settings -> Branches -> Add rule
285-
2. Branch name pattern: `main`
286-
3. Enable:
287-
- "Require a pull request before merging"
288-
- "Require approvals" (1 minimum)
289-
- "Require status checks to pass before merging"
290-
- Select required status check: "test"
291-
4. Save changes
299+
- PyPI Trusted Publisher: configured for quantbai/elvers -> publish.yml
300+
- GitHub Branch Protection on main: require PR, require CI status checks
301+
- GitHub Actions: ci.yml (push/PR) + publish.yml (tag-triggered release)
292302

293-
### Local Development Setup (every developer)
303+
### Local Development Setup (every new developer)
294304

295305
```bash
296306
git clone https://github.com/quantbai/elvers.git
@@ -302,33 +312,15 @@ pre-commit install
302312

303313
---
304314

305-
## 11. Commands Reference
315+
## 11. Quick Reference
306316

307317
```bash
308-
# === Setup ===
309-
pip install -e ".[dev]" # Install with dev dependencies
310-
pre-commit install # Install git hooks
311-
312-
# === Daily Development ===
318+
pip install -e ".[dev]" # Setup
319+
pre-commit install # Git hooks
313320
pytest tests/ -v # Run all tests
314-
pytest tests/test_timeseries.py -v # Single file
315-
pytest tests/test_timeseries.py::TestTsProduct -v # Single class
316-
ruff check elvers/ # Lint check
317-
ruff check elvers/ --fix # Auto-fix lint issues
321+
pytest tests/test_timeseries.py::TestTsProduct -v # Single test class
322+
ruff check elvers/ --fix # Lint + auto-fix
318323
ruff format elvers/ # Format code
319-
320-
# === Git ===
321-
git status # See what changed
322-
git diff # See actual changes
323-
git add <files> # Stage specific files (never git add -A)
324-
git commit -m "type(scope): msg" # Commit with convention
325-
git push origin <branch> # Push to remote
326-
git log --oneline -10 # Recent history
327-
328-
# === Release ===
329-
python -m build # Build package locally (for testing)
330-
git tag v0.2.0 # Create version tag
331-
git push origin v0.2.0 # Push tag (triggers publish)
332324
```
333325

334326
---

0 commit comments

Comments
 (0)