feat(vortex): Vortex Indicator, paired trend lines over summed true range (#349) - #377
Conversation
|
Reviewed at Blocker — the zero-denominator guard never tests the denominator
if( nullRun >= optInTimePeriod ) { 0.0; 0.0; }
else { outPlusVI[outIdx] = curVMP / curTR; ... }
Reproducer: 40 bars, all That matters beyond the arithmetic: VORTEX does not set Both precedents the comment cites do more than VORTEX does. You are right to reject Documentation
Happy to push the fixes on top rather than send them back to you — say which you prefer. |
|
I'll fix — the reproducer is exactly right, and I only took half of ULTOSC's pattern (the count, not the reseed-plus-exact-division-gate). Fixes and the absorption case as a test leg incoming on this branch; ER (#350, in progress) gets the same full pattern. |
de9321a to
d31272d
Compare
|
Fixed at Blocker: the guard is now ULTOSC's full pattern, both halves — the flat-run reseeds all three sums to exactly 0.0 (purging the add/subtract residue), and the division is gated on the denominator itself with an exact Docs: the fence is flattened into prose (the Formula section now reads as sentences with inline code), the Parameters bullet no longer restates the default, and The same lesson is already applied to ER (#350, in progress): its guard is current kama.c's #253 form — the issue's "verbatim kama.c" snippet predates that fix — with the same reseed-plus-exact-comparison shape. |
b24eda7 to
c9d311b
Compare
|
Blocker in the reseed — and the bad advice was mine. Apologies. My earlier comment said to apply "the ULTOSC pattern: reseed on the flat run and guard the division with an exact The defect
if( trueRange == 0.0 ) nullRun++; else nullRun = 0;
if( nullRun >= optInTimePeriod ) { sTR = 0.0; sVMP = 0.0; sVMM = 0.0; }
sVMP += fabs( inHigh[today] - inLow[today-1] );
sVMM += fabs( inLow[today] - inHigh[today-1] );read bar Reproducer, well-formed OHLC throughout ( gives At the default period the same shape (one normal bar, a 14-bar halt, then resumption) permanently poisons both lines: bars 15-17 come out Why ULTOSC is not the precedent I claimed
The fixThe load-bearing half of the original fix was the exact denominator gate, and that is correct as written — keep Reseed only The comment block needs the same correction — as written it cites ULTOSC's reseed as validated precedent, which is the reason this reads as reviewed rather than as a defect, and it ships into all four backends. Two smaller things
None of the existing legs catch the numerator corruption: leg 3 uses |
|
Fixed at
|
c9d311b to
07492f7
Compare
…ange (TA-Lib#349) +VI = SUM(|H - prevL|, n) / SUM(TR, n), -VI = SUM(|L - prevH|, n) / SUM(TR, n) -- Botes & Siepman, TASC 28:1 (Jan 2010). Defaults 14, price bundle HLC, two line outputs, no smoothing, no recursion, no unstable period; bar 0 is consumed exactly as TA_TRANGE consumes it, so the first output sits at index n, not n-1. Zero-denominator handling, refined twice in review: - The division is gated on the DENOMINATOR itself with an exact `curTR > 0.0`. A flat-bar count alone is only a proxy for sTR == 0 in exact arithmetic -- floating-point absorption can zero the running sum while the window still holds a live term, and the ungated division emits NaN/Inf on a function that does not declare them. An absolute TA_IS_ZERO band fails the QUOTE-UNIT/SCALE gate instead (VORTEX is homogeneous of degree 0). - The flat-run purge zeroes ONLY sTR: nullRun counts exactly-zero TRUE RANGES, which proves every TR term in the window is zero -- it proves nothing about the vortex terms, which read the PREVIOUS bar's extremes and stay alive across a halt that follows a spread bar. Zeroing the numerator sums there poisons both lines permanently (an unreachable negative -VI). ULTOSC can reseed all its totals because its predicate covers both of its per-bar terms; VORTEX's covers only the denominator's. The loop writes its outputs LAST: the trailing subtraction re-reads bars trailingIdx-1 and trailingIdx, which sit exactly at and one past the output slot, and the caller may alias any output onto any input. No gate in the suite can catch an emit-first order, so test_vortex.c aliases each of the 2 outputs onto each of the 3 inputs, bit-compared. Tests (test_vortex.c): the bit-exact differential against TA_TRANGE + TA_SUM + hand abs-diff numerators over a 6-period x 6-start grid (4000-value floor, TA_SUM anchored at the clamped start, n=1 direct); the issue's frozen triple-sourced goldens at rel 1e-12; all-flat guard non-vacuity; the absorption reproducer with bar 34 pinned exactly (the one bar where the exact gate and a flat-count proxy disagree); the halt-resume reproducer (a spread bar, a full flat window, resumption) asserting non-negative lines everywhere and the resumed values against a naive fresh-sum reference; and startIdx==endIdx / clamping / n=1 edges. The ta4j and trading-signals oracle arms named by the issue live in the oracle infrastructure outside this repository; the goldens above carry the formula proof here. CHANGELOG entry added.
07492f7 to
c7cfdc0
Compare
|
Correction to my last comment, plus the fix, at The sabotage claim I made ("with the all-sums reseed re-introduced, that leg fails at the first resumed bar") was wrong as first shipped — when I actually ran it, the leg stayed green. My halt was one bar too long: with resumption a bar later than yours, the all-sums purge re-fires on the extra flat bar and launders the negative sums before any live TR exposes them, so the corruption self-erases and the leg discriminates nothing. Fixed by moving resumption to bar 15 exactly — the flat run first reaches n at bar 14, the purge fires while bar 1's live vortex terms are still in the window and about to be retired, and the very next bar divides by a live TR before any later purge can launder the sums. Now verified both ways: with the all-sums reseed re-introduced the leg fails at bar 15 with |
Implements #349: VORTEX —
+VI = SUM(|H − prevL|, n) / SUM(TR, n),−VI = SUM(|L − prevH|, n) / SUM(TR, n)(Botes & Siepman, TASC 28:1). Defaults 14, HLC bundle, twolineoutputs,flags: [stream](the classifier folded the trailing-recompute shape on the first try — ULTOSC's two-lag precedent covered it), no unstable period. First output at indexn, bar 0 consumed exactly asTA_TRANGEconsumes it.One deviation from the issue's decision 2, forced by a gate
The guard is not
TA_IS_ZERO. The QUOTE-UNIT/SCALE gate rejects it: VORTEX is homogeneous of degree 0, so a power-of-two quote-unit change must be bit-identical — and the absolute 1e-14 band zeroes a legitimate ratio at 2^-120 (the gate fails exactly there, measured). The issue's ULTOSC citation predates #253; ULTOSC itself now counts flat bars. This body does the same: a per-bartrueRange == 0.0test is exact and scale-invariant, andnullRun >= nrecognizes the all-flat window with no absolute threshold. The all-flat behaviour is unchanged (both lines exactly 0.0), and both external implementations agree there.Aliasing is the hand-written test's reason to exist
The loop writes its outputs last: the trailing subtraction re-reads bars
trailingIdx−1andtrailingIdx— exactly at and one past the output slot — and TA-Lib permits aliasing any output onto any input. No generic gate exercisesinput == output, and all four backends inherit the order from the one input.c, so--xlang-hashwould agree bitwise on a wrong answer.test_vortex.caliases each of the 2 outputs onto each of the 3 inputs and bit-compares against the separate-buffer call.Tests (
test_vortex.c, registered in CMakeLists + Makefile.am +DO_TEST)memcmp): fused vsTA_TRANGE+TA_SUM+ hand abs-diff numerators, 6 periods × 6 startIdx, ≥4000-value floor. Two reference-side subtleties the grid surfaced:TA_SUMmust be anchored at the fused loop's clamped start (its running sum accumulates rounding differently from a fresh prime — measured 1e-15-class drift at n=100 when rolled from term 0; same class of issue as COPPOCK's WMA re-anchor phase), andTA_SUM's period floor is 2, son=1uses the terms directly.startIdx == endIdx(bit-equal to the full-range bar), lookback clamping,n=1.Verification
Full
--codegen(all four languages) rc 0 including the QUOTE-UNIT/SCALE leg that rejected the first guard;stream_verifygreen;--xlang-hashzero non-baseline mismatches (no transcendentals —fabs/comparisons only — so noCODEGEN_TRANSCENDENTAL[]entry); full generator suite + clippy +regen-check+check-source-listsclean. The ta4j / trading-signals oracle arms named by the issue live in the oracle infrastructure outside this repo; the goldens above carry the formula proof here. CHANGELOG entry added.