@@ -36,6 +36,8 @@ elvers/
3636tests/
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
220244ruff 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
229253git add elvers/__init__.py CHANGELOG.md
230- git commit -m " release: v0.2.0 "
254+ git commit -m " release: vX.Y.Z "
231255git 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)
239263git checkout main
240264git 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
2562801 . ` .github/workflows/publish.yml ` triggers
2572812 . Runs full test suite on Python 3.10-3.13 (safety net)
2582823 . If tests pass: builds package, publishes to PyPI
2592834 . 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
296306git 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
313320pytest 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
318323ruff 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