Skip to content

feat(analytics): rolling beta & market-factor exposure report (#352) - #417

Merged
Abidoyesimze merged 1 commit into
Neurowealth:mainfrom
BernardOnuh:feat/352-factor-exposure
Sep 5, 2026
Merged

feat(analytics): rolling beta & market-factor exposure report (#352)#417
Abidoyesimze merged 1 commit into
Neurowealth:mainfrom
BernardOnuh:feat/352-factor-exposure

Conversation

@BernardOnuh

Copy link
Copy Markdown
Contributor

Closes #352

Summary

Connects the previously-unfed beta in metrics.ts to the DeFi-yield "market factor": a rolling beta & market-factor exposure report that measures how much of a portfolio's yield movement is explained by the canonical market factor versus idiosyncratic protocol selection, computed on a rolling window so exposure can be seen changing over time.

What changed

  • src/analytics/benchmark.ts (new) — the single canonical definition of "the market": buildMarketFactorSeries produces a daily benchmark return series on the aligned grid. Equal-weighted by default, with a pluggable TVL-weighted alternative that falls back to equal (flagged) when no TVL is available. Reuses backtest.buildDailyRateSeries forward-fill so it stays index-aligned with every other series.
  • src/analytics/attribution.ts — refactored to import buildMarketFactorSeries instead of re-deriving the market inline (one definition, not two). A golden test pins attribution's benchmark return so a future benchmark change can never silently alter an attribution number.
  • src/analytics/factorExposure.ts (new) — pure, zero-I/O core: rollingBeta (per-window OLS → { beta, alpha, rSquared, sampleCount }, null on under-sample/zero-variance) + factorDecomposition (full-window { beta, alpha(annualized), rSquared, idiosyncraticVolShare }). Never NaN/Infinity.
  • src/analytics/factorExposureService.ts (new) — DB glue: reads the user's YieldSnapshot value buckets + the benchmark universe's ProtocolRate history, intersects both on a shared daily grid (never zero-fill), caps retention at 90d, returns a deterministic inputHash.
  • src/routes/analytics.tsGET /api/v1/analytics/factor-exposure?window=&rollingWindow=&weighting= (owner-scoped via req.auth.userId). Enforces rollingWindow < window (400 otherwise); fewer than 2 rolling windows returns the summary only with a caveat; every response ships the fixed yield-co-movement caveat.
  • Docs — new §5 in docs/PERFORMANCE_ATTRIBUTION.md; docs/openapi.yaml path (+ validated with @redocly/cli).

Acceptance criteria coverage

  • One canonical buildMarketFactorSeries; attribution imports it with a golden test
  • rollingBeta returns per-window { beta, alpha, rSquared, sampleCount }, null on under-sample/zero-variance
  • factorDecomposition returns full-window beta/alpha/R²/idiosyncratic-vol-share
  • GET /analytics/factor-exposure owner-scoped, retention-bounded, rollingWindow < window enforced, mandatory caveat
  • Series intersected on shared grid (never zero-filled); deterministic with inputHash
  • Pure cores zero-I/O, fixture-tested (beta ~0 / ~1 / null)
  • Docs + openapi updated; unit + integration tests green

Verification

npm run typecheck, npm run lint, npm run format:check, npm run validate:spec, and the full suite (124 suites / 1672 tests) all pass.

@drips-wave

drips-wave Bot commented Sep 1, 2026

Copy link
Copy Markdown

@BernardOnuh Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Abidoyesimze
Abidoyesimze merged commit 96b7224 into Neurowealth:main Sep 5, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rolling Beta & Market-Factor Exposure Reporting

3 participants