fix: de-duplicate four indicators by correcting their definitions (#300)

* fix(core): de-duplicate 3 indicators by correcting their definitions

Behavioral audit found these computed identically to another indicator:

- AverageDrawdown was the mean per-bar under-water fraction = PainIndex.
  Now the conventional average drawdown: mean of the maximum depths of the
  distinct drawdown episodes in the window.
- IntradayIntensity was a cumulative line = the A/D Line (Adl); its normalized
  form is the Chaikin Money Flow (Cmf). Now the raw per-bar Bostian intensity
  volume*(2c-h-l)/(h-l), distinct from both.
- AwesomeOscillatorHistogram was AO - SMA(AO, n) = AcceleratorOscillator. Now
  the AO momentum AO[t] - AO[t-lookback] (the histogram delta); the 3rd
  parameter is reinterpreted from sma_period to lookback (default 1).

Constructor signatures are unchanged, so the bindings keep their API. Core
unit tests rewritten with the new reference values; workspace tests + clippy
green. Binding value-tests and deep-dive docs are updated separately.

* fix(core): redefine AdOscillator as the A/D Oscillator (was a Wad duplicate)

AdOscillator computed the cumulative volume-free Williams A/D line, identical
to the Wad indicator. Redefine it as the Williams A/D *Oscillator*: the same
line minus its 13-bar SMA, so it oscillates around zero (mean-reverting) while
Wad stays the drifting cumulative line for divergence analysis. The canonical
name AdOscillator is now accurate; the trait name() becomes "ADOSC".

Constructor stays no-arg (internal 13-bar signal). Unit tests rewritten and
cross-checked against Wad - SMA(Wad, 13). The native bindings' "WilliamsAD"
alias is renamed to "ADOSC" separately.

* fix(bindings): rename WilliamsAD alias to ADOSC and update value tests

Follows the core de-duplication: the native bindings exposed the Williams A/D
line as 'WilliamsAD', which is now the A/D Oscillator. Rename the Python /
Node.js / WASM alias to 'ADOSC' (regenerated node index.js / index.d.ts) and
update the binding value-tests for the four redefined indicators
(AverageDrawdown episode mean, AwesomeOscillatorHistogram momentum warmup,
the Wad-line reference test now uses ta.Wad()). Python suite and node suite
both pass (pytest all green, node 584/584).

* docs: record indicator de-duplication in README and CHANGELOG

README volume family: 'Williams A/D' -> 'Williams A/D Oscillator', 'Intraday
Intensity Index' -> 'Intraday Intensity'. CHANGELOG [Unreleased] documents the
four redefinitions and the native WilliamsAD -> ADOSC rename as breaking.

* test(core): cover Default impl and drop dead match arm

Codecov flagged AdOscillator::default() (never exercised) and the unreachable
_ => panic!() arm in the AwesomeOscillatorHistogram test. Exercise Default in
the accessors test and rewrite the histogram check as an if-let, removing the
dead arm.
This commit is contained in:
kingchenc
2026-06-15 03:41:15 +02:00
committed by GitHub
parent a3950bf31b
commit 82d7479011
15 changed files with 320 additions and 208 deletions
+3 -3
View File
@@ -259,7 +259,7 @@ const candleScalar = {
VolumeOscillator: { make: () => new wickra.VolumeOscillator(14, 28), step: (ind, i) => ind.update(volume[i]), batch: (ind) => ind.batch(volume) },
NVI: { make: () => new wickra.NVI(), step: (ind, i) => ind.update(close[i], volume[i]), batch: (ind) => ind.batch(close, volume) },
PVI: { make: () => new wickra.PVI(), step: (ind, i) => ind.update(close[i], volume[i]), batch: (ind) => ind.batch(close, volume) },
WilliamsAD: { make: () => new wickra.WilliamsAD(), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
ADOSC: { make: () => new wickra.ADOSC(), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
AnchoredVWAP: { make: () => new wickra.AnchoredVWAP(), step: (ind, i) => ind.update(high[i], low[i], close[i], volume[i]), batch: (ind) => ind.batch(high, low, close, volume) },
DemandIndex: { make: () => new wickra.DemandIndex(10), step: (ind, i) => ind.update(high[i], low[i], close[i], volume[i]), batch: (ind) => ind.batch(high, low, close, volume) },
TSV: { make: () => new wickra.TSV(18), step: (ind, i) => ind.update(close[i], volume[i]), batch: (ind) => ind.batch(close, volume) },
@@ -1009,8 +1009,8 @@ test('AwesomeOscillatorHistogram on a flat median converges to zero', () => {
Array(n).fill(11),
Array(n).fill(9),
);
// warmup = 5 + 3 - 1 = 7.
for (let i = 6; i < n; i++) assert.ok(Math.abs(out[i]) < 1e-12);
// AO momentum; warmup = slow + lookback = 5 + 3 = 8.
for (let i = 7; i < n; i++) assert.ok(Math.abs(out[i]) < 1e-12);
});
test('STC on a flat series stays at zero', () => {
+2 -2
View File
@@ -2505,8 +2505,8 @@ export declare class KVO {
isReady(): boolean
warmupPeriod(): number
}
export type AdOscillatorNode = WilliamsAD
export declare class WilliamsAD {
export type AdOscillatorNode = ADOSC
export declare class ADOSC {
constructor()
update(high: number, low: number, close: number): number | null
batch(high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -5926,7 +5926,7 @@ impl KvoNode {
// ============================== Williams A/D ==============================
#[napi(js_name = "WilliamsAD")]
#[napi(js_name = "ADOSC")]
pub struct AdOscillatorNode {
inner: wc::AdOscillator,
}