fix: Correct inverted ratio returned by obsolete GetPrs - #2210
Merged
Conversation
Contributor
|
|
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Duplication | 0 |
🟢 Coverage ∅ diff coverage · -0.01% coverage variation
Metric Results Coverage variation ✅ -0.01% coverage variation (-1.00%) Diff coverage ✅ ∅ diff coverage Coverage variation details
Coverable lines Covered lines Coverage Common ancestor commit (84f8285) 19562 18879 96.51% Head commit (e92820e) 19602 (+40) 18915 (+36) 96.50% (-0.01%) Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch:
<coverage of head commit> - <coverage of common ancestor commit>Diff coverage details
Coverable lines Covered lines Diff coverage Pull request (#2210) 0 0 ∅ (not applicable) Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified:
<covered lines added or modified>/<coverable lines added or modified> * 100%
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.
This was referenced Aug 23, 2026
Closed
Closed
The obsolete v3 GetPrs shim passed its two source series in the wrong order, so it returned base / eval - the reciprocal of the Price Relative Strength ratio the library documents. Its documented default lookback also threw instead of computing. Both shipped in 3.0.0 and are silent: the method is Obsolete(.., false), so it compiles with a warning and runs. RETURNED VALUES CHANGE for code still calling GetPrs. That is the point: those callers are receiving wrong numbers today. Callers of ToPrs were never affected. Measured on the repo's test data, GetPrs(eval, base, 20) returned 1.3568166993 where ToPrs(eval, base, 20) returns 0.7370192308 - exactly the reciprocal, and exactly ToPrs(base, eval). This restores pre-3.0.0 behavior rather than choosing new behavior. Before the 3.0.0 rewrite, GetPrs(quotesEval, quotesBase, ..) called CalcPrs(tpListEval, tpListBase, ..) in all three overloads, and CalcPrs computed eValue / bValue. docs/indicators/prs.md states the same ratio direction. The shim's own Obsolete message says 'Rename GetPrs(..) to ToPrs(..)', and a rename asserts identical semantics, so an inversion cannot have been intended. Also restores the unspecified lookback. It was mapped to 0, which Validate rejects, making the shim's own default throw. It now calls the two-argument ToPrs overload, which is the library's own expression of 'compute no PrsPercent', rather than replicating that overload's internal int.MinValue sentinel. The tuple overload knowingly diverges from v2 by also accepting an explicit 0: the 3.0.0 signature narrowed v2's int? to int = 0, leaving 0 as the only marker for unspecified, and restoring the default has to honor it. Pinned by a test. Adds the first tests to call GetPrs at all, including absolute anchored values so a co-regression in ToPrs cannot pass unnoticed, and a reflection test for the Obsolete(.., true) overload, which C# cannot call and whose CS0619 is not suppressible. Documents both corrections in docs/migration/v3.md, naming the affected method, the observable change in output, and the version they land in, per the correctness principles. Closes #2208
DaveSkender
force-pushed
the
fix-v3-prs-shim
branch
from
August 24, 2026 00:50
caf52bb to
e92820e
Compare
This was referenced Aug 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2208.
Important
Returned values change for code still calling
GetPrs. In 3.0.0 it returnedbase / eval— the reciprocal of the ratio this library documents. It now returnseval / base. If you calledGetPrson 3.0.x, stored values and any thresholds tuned against them must be revalidated; new values are the reciprocal of old ones. Callers ofToPrswere never affected.Separately,
GetPrs(eval, base)with nolookbackPeriodspreviously threwArgumentOutOfRangeExceptionand now computes with a nullPrsPercent, as it did before 3.0.0.The defects
Inverted ratio. The receiver of
.ToPrs(..)binds tosourceEval, and the shim passedquotesBasethere (Obsolete.V3.Indicators.cs:683-684on main). Measured on the repo's test data:Unusable default.
lookbackPeriodswas mapped to0, whichPrs.Utilities.cs:28rejects (is <= 0 and not int.MinValue), so the shim's own documented default threw.Why this is a bug fix, not a behavior change
This restores what the method returned before 3.0.0. It does not choose new behavior.
Before the 3.0.0 rewrite, all three
GetPrsoverloads calledCalcPrs(tpListEval, tpListBase, …)— eval first — andCalcPrscomputedPrs = (bValue == 0) ? null : (eValue / bValue), witheValuefromtpListEval. (src/m-r/Prs/Prs.Api.csandsrc/m-r/Prs/Prs.Series.csat896ae089^.) So V2 returned eval ÷ base, and a caller who upgraded to 3.0.0 without changing a line silently began receiving the reciprocal.Three further corroborations:
docs/indicators/prs.mddefinesPrsasEval / Base, with a worked example"Rename GetPrs(..) to ToPrs(..)", and a rename asserts identical semantics — it cannot carry a deliberate redefinitionGetBetaandGetCorrelationshims order their arguments correctly, so this is an isolated slip rather than a patternThe likely mechanism is visible in the V2 source: it declared
tpListBasebeforetpListEvalbut passed eval first, and the shim appears to have transcribed declaration order into call order.The counter-argument, and why it was dismissed
A consumer on 3.0.0 since June may have unknowingly built on the reciprocal, and their numbers move. That was weighed and rejected:
[Obsolete]means the method's entire contract is "behaves as V2 did" — the reciprocal is not a behavior anyone choseThis is exactly the case #2215 added to
docs/PRINCIPLES.md: correcting a defect, where the shipped behavior never matched its own documentation, is a bug fix and versions as one. It lands in 3.0.1. The disclosure obligation still applies in full — the callout above and the new Corrections to obsolete v2 shims section indocs/migration/v3.mdname the affected method, the observable change in output, and the version it lands in.Worth noting these shims are themselves removed in
3.1. Correcting them still matters, because3.0.xis the version the migration guide tells users to land on before upgrading further — so it has to be right.Implementation note
The unspecified lookback now calls the two-argument
ToPrsoverload, which is the library's own public expression of "compute noPrsPercent", rather than replicating that overload's internalint.MinValuesentinel in the shim. If the sentinel ever changes, the shim follows automatically.The tuple overload knowingly diverges from V2 on one point: it also accepts an explicit
0, which V2 rejected. The 3.0.0 signature narrowed V2'sint?toint = 0, so0is the only marker left for "unspecified", and restoring the default has to honor it. Restoring theint?signature instead would break already-compiled callers rather than fix them. Pinned byGetPrsFromTuplesTreatsZeroAsUnspecified.Tests
These are the first tests to call
GetPrsat all. No test anywhere referenced it and every overload carries[ExcludeFromCodeCoverage]— that is why two defects survived a major release.Ten tests, including absolute anchored values so a co-regression in
ToPrscannot pass unnoticed (every other assertion compares the shim againstToPrs, so both sides would move together), explicit-0and negative-lookback pins, and a reflection test for theObsolete(.., true)overload — C# cannot call it andCS0619is not suppressible, so reflection is the only way it can be verified.Mutation-verified:
0GetPrsWithUnspecifiedLookbackComputesWithoutPercentfailsErrorLevelGetPrsOverloadAppliesTheSameLookbackMappingfailsdotnet build --no-incremental -c Release— 0 errors, 0 new warnings (15NU1507are pre-existing package-source config)dotnet test -c Release— 2542 / 76 / 4 passing, 0 failedReviewed
Three specialist lanes. The restoration claim was independently verified against the V2 source by two of them. Review changed the implementation (dropping a private sentinel constant in favor of the two-argument overload), added four tests, and struck a rationale of mine that was factually wrong — I had argued the
Obsolete(.., true)site was worth patching becauseCS0619is suppressible; it is not. The site is still worth patching because reflection reaches it, which the new test demonstrates.A second, worse defect of the same class is out of scope here and not yet filed:
GetPvo's shim declaresfastPeriods = 9, slowPeriods = 12where both V2 andToPvodeclare12and26.bars.GetPvo()returns1.4387against a correct10.4395— silently, with no exception. Raised for routing rather than folded in, since it needs its own tracked record.