Skip to content

feat: Chart Correlation and Price Relative Strength - #559

Draft
DaveSkender wants to merge 2 commits into
mainfrom
498-correlation-prs-endpoints
Draft

feat: Chart Correlation and Price Relative Strength#559
DaveSkender wants to merge 2 commits into
mainfrom
498-correlation-prs-endpoints

Conversation

@DaveSkender

Copy link
Copy Markdown
Member

Closes #498.

Summary

The last two chart-less indicator pages — Correlation and Prs — now have catalog entries and endpoints. That leaves Renko as the only remaining item, and it has a proposed "not charted" decision recorded below.

The blocker was a false premise

#498 recorded these as blocked on API shape:

the current single-series endpoint shape doesn't express "indicator vs. benchmark," so these may need a new parameter (e.g. a second symbol) before they're chartable

That is not the case. BETA has served exactly this shape since it was added — it fetches SPY as a second series and publishes an ordinary oscillator listing with no benchmark parameter. And the library signatures line up exactly:

Method Signature
ToBeta (sourceEval, sourceMrkt, lookbackPeriods, type)
ToCorrelation (sourceA, sourceB, lookbackPeriods)
ToPrs (sourceEval, sourceBase[, lookbackPeriods])

Same argument order, benchmark second. So both follow the existing pattern with no API redesign, and #474 (OpenAPI spec) is unblocked — it was parked behind this question.

Charting decisions

Correlation charts correlation and rSquared on one pane. Both are dimensionless (−1..1 and 0..1), so a shared y-axis does not mislead — unlike SMA analysis, which was split per-metric precisely because its units differed. A dashed line at zero marks where the security stops tracking the benchmark.

PRS charts prs only. prsPercent is a percentage and would need its own axis — the same mixed-unit problem — so it is omitted rather than crammed in. Since lookbackPeriods only drives prsPercent, the endpoint takes no parameters at all rather than exposing a knob with no visible effect. No threshold line: PRS is an unnormalized price ratio, so no fixed value marks equal performance, and inventing a reference line would misinform.

Refactor at the point of contact

The benchmark fetch was about to become its third copy, so it moved into a GetVsBenchmark helper beside the existing single-series Get, and BETA now routes through it. Same route, same parameters, same response shape — verified below.

A finding this surfaced (not fixed here)

QuoteBackup.BackupQuotes is a single symbol-agnostic dataset. When storage is unavailable, LoadQuotesAsync returns the same bars for both QQQ and SPY, so every benchmark indicator degenerates — correlation becomes exactly 1.0 at every point. This is pre-existing and already affects BETA; Correlation just makes it obvious, because a flat 1.0 line reads as broken.

Production is unaffected — R2 carries both symbols, confirmed by the live BETA response below returning 1.20–1.78. Reporting rather than fixing, since the failover dataset is its own concern.

Test plan

  • Backend tests 69/69 (65 existing + 4 new)
  • dotnet build Charts.sln -warnAsError — 0 warnings, 0 errors
  • Roslynator — 0 diagnostics (it caught one Assert.IsAssignableFrom in my new test; switched to Assert.IsType(…, exactMatch: false))
  • dotnet format --verify-no-changes — clean
  • Live API: both endpoints return 120 rows; CORRELATION values stay within [−1, 1]
  • Catalog serves 98 listings including both new entries, with dataNames matching the JSON payload
  • BETA regression: unchanged response keys and values after the refactor
  • Invalid input still returns 400 through the new helper
  • Mutation-tested the new guards — pointing the helper at its own series fails all 4 new tests
Execution output
$ curl 'localhost:5000/indicators'
  CORRELATION: name='Correlation Coefficient (vs SPY)' chartType=oscillator params=1 results=['correlation', 'rSquared']
  PRS:         name='Price Relative Strength (vs SPY)' chartType=oscillator params=0 results=['prs']
  BETA:        name='Beta' chartType=oscillator params=1 results=['beta', 'betaUp', 'betaDown']

$ curl 'localhost:5000/PRS' | tail -1
  {"timestamp": "2018-12-31T00:00:00", "prs": 1, "prsPercent": null}

$ curl 'localhost:5000/CORRELATION?lookbackPeriods=0' -o /dev/null -w '%{http_code}'
  400

# production, proving the benchmark series is real there
$ curl 'https://charts-api.stockindicators.dev/BETA?lookbackPeriods=50&type=Standard'
  rows: 120   beta range: 1.2030 .. 1.7801   all exactly 1.0 (degenerate)? False

# mutation check — benchmark replaced with the evaluated series
  failed BenchmarkIndicators_RequestTheBenchmarkSeries(indicator: "CORRELATION")
  failed BenchmarkIndicators_RequestTheBenchmarkSeries(indicator: "PRS")
  failed BenchmarkIndicators_RequestTheBenchmarkSeries(indicator: "BETA")
  failed GetCorrelation_WithDistinctBenchmark_DoesNotReturnPerfectCorrelation
  total: 69  failed: 4  succeeded: 65   ← restored, now 69/69

Renko — recording the "not charted" decision

Adopting the proposal already made on #498, which the issue body explicitly sanctions: Renko bricks are not 1:1 with quotes, and the chart architecture windows every indicator dataset by quote index, so a brick series cannot be windowed without a per-indicator axis model. Charting it needs its own pane type with an independent x-axis, which is out of scope for the catalog shape. This flips if a pane type with an independent axis is ever built for another reason.

Notes

  • Docs-side wiring is a one-line <StockIndicatorChart indicator="…" /> per page, in the docs repository, plus an indy-charts release carrying nothing new — both catalog entries use existing chart types.
  • The benchmark is SPY and fixed: QuoteService serves only the symbols the scheduled refresh maintains, so a caller-supplied symbol would have no data behind it.

Both indicators compare a security against a market benchmark, and #498
recorded them as blocked on an API-shape question: the single-series endpoint
was thought unable to express "indicator vs. benchmark", needing a new
second-symbol parameter first.

That premise does not hold. BETA has served exactly this shape since it was
added — it fetches SPY as a second series and publishes an ordinary oscillator
listing with no benchmark parameter. Verified against production, where BETA
returns 1.20–1.78 rather than a degenerate constant, so the benchmark series
is real there. Correlation and PRS take the same `(evaluated, benchmark, …)`
argument order as ToBeta, so both follow the existing pattern with no API
redesign.

The benchmark fetch would have been its third copy, so it moves into a
`GetVsBenchmark` helper alongside the existing single-series `Get`, and BETA
now routes through it. That is the whole of the BETA change: same route, same
parameters, same response shape.

Correlation charts `correlation` and `rSquared` together — both dimensionless,
so one y-axis reads honestly — with a dashed zero line, the point where the
security stops tracking the benchmark. PRS charts only `prs`; `prsPercent` is
a percentage and would need its own axis, the same mixed-unit problem that
split SMA analysis into per-metric listings. PRS gets no threshold line: it is
an unnormalized price ratio, so no fixed value marks equal performance, and an
invented reference would misinform.

The tests pin the property that no status code reveals. When the benchmark
resolves to the same bars as the evaluated security the maths still succeeds,
returning a constant 1.0 — so a case asserts a divergent benchmark does not
correlate perfectly, and a theory asserts all three endpoints request the
benchmark by symbol. Confirmed load-bearing: pointing the helper back at its
own series fails all four.
@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: facioquo/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 23a52e69-808e-421a-8bca-5f882e9ce7f4

📥 Commits

Reviewing files that changed from the base of the PR and between 4e5e7bf and f38ab55.

📒 Files selected for processing (3)
  • server/WebApi.Tests/Endpoints/MainEndpointsTests.cs
  • server/WebApi/Endpoints.cs
  • server/WebApi/Services/Service.Metadata.cs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change adds shared SPY benchmark handling for BETA, CORRELATION, and PRS. It introduces CORRELATION and PRS endpoints and registers both indicators in the catalog. The catalog defines their chart types, parameters, thresholds, and results. Tests verify separate benchmark requests, successful responses, and non-perfect correlation with divergent benchmark data.

Assessment against linked issues

Objective Addressed Explanation
Add catalog listings and data endpoints for Correlation Coefficient / R² and PRS [#498]
Add a catalog listing and data endpoint for Renko [#498] No Renko endpoint or catalog listing was added.

Merge Risk: ⚪ Minimal · up to f38ab

This PR adds chart catalog entries and endpoints for Correlation and PRS while preserving existing benchmark behavior; no actionable merge-blocking risk remains after normal checks and review.

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch 498-correlation-prs-endpoints

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity · 0 duplication

Metric Results
Complexity 0
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Two defects from self-review of the previous commit.

The Correlation listing was inserted before ConnorsRSI, but the catalog is
ordered alphabetically by Name and the client renders listings in catalog
order, so the indicator picker showed "Correlation Coefficient" ahead of
"ConnorsRSI". Moved after it.

The new tests were inserted between GenerateSampleQuotes' XML summary and the
method itself, leaving the doc attached to the first test instead. Moved back
onto the helper.
@facioquoqa

facioquoqa Bot commented Aug 29, 2026

Copy link
Copy Markdown

:neckbeard: FaciōQuōqa Mage Reviewer is standing by.

  • Trigger review

@DaveSkender
DaveSkender marked this pull request as draft August 29, 2026 05:59
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.

chart API: expose the remaining unchartable indicators (Renko, Correlation, PRS) so their docs pages can chart

1 participant