Skip to content

fix(cci,correl,willr): divide by the value the guard tests, and make %R's range a guarantee (#395) - #397

Merged
mario4tier merged 2 commits into
devfrom
fix/395
Sep 6, 2026
Merged

fix(cci,correl,willr): divide by the value the guard tests, and make %R's range a guarantee (#395)#397
mario4tier merged 2 commits into
devfrom
fix/395

Conversation

@mario4tier

Copy link
Copy Markdown
Member

Closes #395. The three functions left carrying #390's shape — a guard that establishes one expression while the arithmetic below it divides by another. All three are reachable through the shipped batch API and all three return a wrong answer under TA_SUCCESS.

guarded divided by pre-fix result
CCI tempReal2 0.015*tempReal2 ±Inf, and silently imprecise well before that
CORREL ssX, ssY separately sqrt(ssX*ssY) NaN, or ±1.0 from a degenerate window
WILLR diff (exact != 0.0) diff = (h-l)/-100 0 for a close on the period low; values outside [-100, 0]

Each now divides by the value its guard tests: (tempReal/tempReal2)/0.015, spXY/(sqrt(ssX)*sqrt(ssY)), and ((h-c)/(h-l))*(-100.0) under the scale-relative band #253 established.

WILLR also gets the clamp ruled on in #395. The reformulation alone makes [-100, 0] a theorem for well-formed input — fl(h-c) <= fl(h-l) by monotonicity, so the quotient is in [0,1] under every rounding mode. The clamp extends it to input TA-Lib does not validate: a close outside its own bar. willr.md's bound claims stay as written and become true; its formula line is corrected to the divide-first form.

diff leaves the body and the streaming handle of all four backends.

No fourth instance

An exhaustive scan of every division in ta_codegen/input finds none. The Hilbert family matches the shape lexically and is cleared: atan(Im/Re)*rad2Deg can only reach ±Inf, absorbed by the [0.67·prev, 1.5·prev] then [6,50] clamp on the next statement. #396's seven rows are rewritten from "reachability unproven" to that clearance.

The sweep (on top of #396)

With no live instance left to point at, the_inline_arm_* reconstructs CCI's pre-#395 divisor rather than reading a shipped defect, and pins the third half the other arms pin — silent when nothing bounds an operand. Two soundness fixes on the arm: its whole-divisor test accepted any operator, and its equality arm lacked the exclusivity its siblings carry. CORREL's fixed divisor is annotated rather than special-cased, so reverting correl.c:236 fails twice — the key goes stale and the new divisor arrives unannotated.

Measured, not assumed

LEGACY_TOL instrumented to record every function in one run: CCI 1.42e-14 and WILLR 7.11e-15 are new rows at 5e-14 and 3e-14; CORREL's 3.15e-13 is unchanged, so its #242 row stands. --fuzz-064 gives WILLR its own 5e-14 absolute bound (measured 1.42e-14) rather than leaving it in the FMA bucket, whose 1e-9 is five orders looser than it needs — the reason #338 gives for naming ATR.

Six new legs, each sabotage-proven against a build of the pre-fix arithmetic: CCI subnormal (±Inf → exactly ±1/0.015), CORREL at 2^-283 (NaN, -1, +10, ∓1/sqrt(15)), and WILLR's endpoint, range, denormal window, clamp domain (±10000) and machine-flat window.

Green

Full C suite · generator cargo test + clippy -D warnings · Rust clippy/doc/doctests/tests · --codegen against the frozen oracle (161 passed, 0 failed in each of the four backends; CCI and WILLR both value-compared) · --xlang-hash (338,957 golden cases, four backends bit-identical) · --fuzz-064 (0 failures) · regen-check · source-list/cargo-lock/stream-retcode/candle checks.

Performance

Shipped-build A/B on a Zen 4 laptop, min of 12 alternating rounds × 200 reps, 20k bars, four untouched controls inside ±2%:

ratio
WILLR batch 0.917 (the batch tier loses a division)
WILLR streaming 1.054
CCI 1.115
CORREL 1.142

The streaming +5% is the ALT1 tier paying per bar for a hoist it used to amortise — the same order as KAMA's clamp in #390. CCI's and CORREL's costs are one extra divide and one extra sqrt per bar. Both were checked for a cheaper correct form; neither has one that does not reintroduce an overflow band or retighten a guard #253 deliberately widened. CORREL's is well under the ~25% the deleted comment claimed.

Builds on #396 (@kevinlincg), whose inline-divisor arm is what identifies the CCI and CORREL sites structurally.

https://claude.ai/code/session_01GesMuivQUwX4xHr4hEtnS8

…%R's range a guarantee (#395)

The three remaining functions carrying #390's shape: a guard that establishes one
expression while the arithmetic below it divides by another. All three are
reachable through the shipped batch API and all three return a wrong answer under
TA_SUCCESS.

CCI guards `tempReal2` and divided by `0.015*tempReal2`. The band is RELATIVE and
the underflow is ABSOLUTE, so they cross: below |average| ~ 1.6e-308 the guard
still answers "not flat" while the scaled copy is exactly 0.0. Dividing by the
deviation and scaling after removes the mismatch structurally. It also fixes the
silent half, which is larger than the loud one -- the pre-scaled form loses
precision long before it reaches Inf, measured relative error 7.7e-08 at price
scale 1e-314 rising to 4.8e-01 at 1e-321, against 2e-16 for the new form at every
one of those magnitudes.

CORREL guards ssX and ssY separately -- correctly, and correl.c explains why -- and
divided by `sqrt(ssX*ssY)`. The product carries the fourth power of the window
spread, so it leaves the double range at both ends while both factors are still
ordinary normals with all 53 bits, and no test of the factors can see it. Where
spXY is zero the result is NaN, which the [-1,1] clamp does not catch (`NaN > 1.0`
is false); where it is not, the +-Inf is rewritten to exactly +-1.0, a perfect
correlation reported from a degenerate window. A root of each factor cannot do
either: sqrt maps any finite positive double into [2.3e-162, 1.4e154], so their
product is representable at both ends. That also deletes the comment block arguing
for the one-root form, whose overflow reasoning the tree's own oracle
(ta_test_reference.c) already contradicted.

WILLR is two defects, and the maintainer's ruling on the second is on #395. It
still carried the exact `diff != 0.0` that #107 replaced in STOCH, so a
machine-flat window divided sub-epsilon residue into full-scale noise, and a
denormal range underflowed `diff` to 0.0 and answered 0 -- the value meaning a
close at the period HIGH -- for a close sitting on the period LOW. Separately, the
pre-scaled divisor cost the endpoint exactness that keeps %R in its documented
range: the shipped library returns -100.00000000000001 on the reference series at
periods 2, 3 and 4. Dividing by the range and scaling after makes the bound a
theorem for well-formed input (fl(h-c) <= fl(h-l) by monotonicity, so the quotient
is in [0,1] under every rounding mode); the clamp extends it to input TA-Lib does
not validate, a close outside its own bar. `diff` leaves the body and the
streaming handle of all four backends.

Not scoped here, and checked rather than assumed: an exhaustive scan of every
division in ta_codegen/input finds no fourth instance of the shape. The Hilbert
family matches it lexically and is cleared -- `atan(Im/Re)*rad2Deg` can only reach
+-Inf, which the `[0.67*prev, 1.5*prev]` then `[6,50]` clamp absorbs on the next
statement -- so #396's seven rows are rewritten from "reachability unproven" to
that clearance.

The sweep from #396 now has no live instance to point at, so its self-test
reconstructs CCI's pre-#395 divisor instead of reading a shipped defect, and pins
the third half the other arms pin: silent when nothing bounds an operand.
Two soundness fixes on the arm: its whole-divisor test accepted any operator, and
its equality arm lacked the exclusivity its siblings carry. CORREL's fixed divisor
is annotated rather than special-cased, which makes reverting correl.c:236 fail
twice -- the key goes stale and the new divisor arrives unannotated.

Measured, not assumed. LEGACY_TOL instrumented to record every function in one
run: CCI 1.42e-14 and WILLR 7.11e-15 are new rows at 5e-14 and 3e-14; CORREL's
3.15e-13 is unchanged, so its #242 row stands. --fuzz-064 gives WILLR its own
5e-14 absolute bound rather than leaving it in the FMA bucket, whose 1e-9 is five
orders looser than it needs -- the reason #338 gives for naming ATR. Absolute
because %R is a bounded dimensionless oscillator: its floor is a ULP of 100 at any
input magnitude.

Six new legs, each sabotage-proven against a build of the pre-fix arithmetic:
CCI subnormal (was +-Inf, now exactly +-1/0.015 -- period 2 makes the mean
deviation equal the numerator, so the expectation is an equality); CORREL at
2^-283 (was NaN, -1, +1; now 0 and +-1/sqrt(15)); and WILLR's endpoint, range,
denormal window, clamp domain (was +-10000) and machine-flat window.

Green: full C suite, generator cargo test and clippy -D warnings, Rust
clippy/doc/doctests/tests, --codegen against the frozen oracle (161 passed 0 failed
in each of the four backends, CCI and WILLR both value-compared), --xlang-hash
(338,957 golden cases, four backends bit-identical), --fuzz-064 (0 failures),
regen-check, and the four source/lock/retcode/candle checks.

Shipped-build A/B on a Zen 4 laptop, min of 12 alternating rounds x 200 reps,
20k bars, four untouched controls inside +-2%: WILLR batch 0.917 (the batch tier
LOSES a division), WILLR streaming 1.054, CCI 1.115, CORREL 1.142. The streaming
+5% is the ALT1 tier paying per bar for a hoist it used to amortise, the same
order as KAMA's clamp in #390. CCI's and CORREL's costs are one extra divide and
one extra sqrt per bar; both were checked for a cheaper correct form and neither
has one that does not reintroduce an overflow band or retighten a guard #253
deliberately widened. CORREL's is well under the ~25% the deleted comment claimed.

Closes #395.

Claude-Session: https://claude.ai/code/session_01GesMuivQUwX4xHr4hEtnS8
# Conflicts:
#	ta_codegen/output/java/library/src/main/java/io/github/talib/BuildStamp.java
#	ta_codegen/output/java/tools/TaCodegenServe.java
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.

1 participant