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:
@@ -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', () => {
|
||||
|
||||
Vendored
+2
-2
@@ -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
@@ -5926,7 +5926,7 @@ impl KvoNode {
|
||||
|
||||
// ============================== Williams A/D ==============================
|
||||
|
||||
#[napi(js_name = "WilliamsAD")]
|
||||
#[napi(js_name = "ADOSC")]
|
||||
pub struct AdOscillatorNode {
|
||||
inner: wc::AdOscillator,
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ from ._wickra import (
|
||||
VolumeOscillator,
|
||||
NVI,
|
||||
PVI,
|
||||
WilliamsAD,
|
||||
ADOSC,
|
||||
AnchoredVWAP,
|
||||
DemandIndex,
|
||||
TSV,
|
||||
@@ -780,7 +780,7 @@ __all__ = [
|
||||
"VolumeOscillator",
|
||||
"NVI",
|
||||
"PVI",
|
||||
"WilliamsAD",
|
||||
"ADOSC",
|
||||
"AnchoredVWAP",
|
||||
"DemandIndex",
|
||||
"TSV",
|
||||
|
||||
@@ -9450,7 +9450,7 @@ impl PyKvo {
|
||||
|
||||
// ============================== Williams A/D Oscillator ==============================
|
||||
|
||||
#[pyclass(name = "WilliamsAD", module = "wickra._wickra", skip_from_py_object)]
|
||||
#[pyclass(name = "ADOSC", module = "wickra._wickra", skip_from_py_object)]
|
||||
#[derive(Clone)]
|
||||
struct PyAdOscillator {
|
||||
inner: wc::AdOscillator,
|
||||
@@ -9507,7 +9507,7 @@ impl PyAdOscillator {
|
||||
self.inner.warmup_period()
|
||||
}
|
||||
fn __repr__(&self) -> String {
|
||||
"WilliamsAD()".to_string()
|
||||
"ADOSC()".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -258,9 +258,9 @@ def test_awesome_oscillator_histogram_flat_series_converges_to_zero():
|
||||
n = 50
|
||||
high = np.full(n, 11.0)
|
||||
low = np.full(n, 9.0)
|
||||
out = ta.AwesomeOscillatorHistogram(3, 5, 3).batch(high, low)
|
||||
# warmup = slow + sma - 1 = 5 + 3 - 1 = 7.
|
||||
np.testing.assert_allclose(out[6:], 0.0, atol=1e-12)
|
||||
out = ta.AwesomeOscillatorHistogram(3, 5, 3).batch(high, low) # AO momentum
|
||||
# warmup = slow + lookback = 5 + 3 = 8.
|
||||
np.testing.assert_allclose(out[7:], 0.0, atol=1e-12)
|
||||
|
||||
|
||||
def test_stc_constant_series_yields_zero():
|
||||
@@ -521,10 +521,10 @@ def test_calmar_ratio_known_path():
|
||||
|
||||
|
||||
def test_average_drawdown_known_window():
|
||||
# window [100, 120, 90, 110]: dd = 0, 0, 0.25, 10/120;
|
||||
# mean = (0.25 + 10/120) / 4.
|
||||
# window [100, 120, 90, 110]: one drawdown episode (peak 120, trough 90),
|
||||
# never recovering -> depth (120-90)/120 = 0.25; one episode -> AvgDD = 0.25.
|
||||
out = ta.AverageDrawdown(4).batch(np.array([100.0, 120.0, 90.0, 110.0]))
|
||||
expected = (0.25 + 10.0 / 120.0) / 4.0
|
||||
expected = 0.25
|
||||
assert math.isclose(out[-1], expected, rel_tol=1e-12)
|
||||
|
||||
|
||||
|
||||
@@ -634,8 +634,8 @@ CANDLE_SCALAR = {
|
||||
lambda: ta.PVI(),
|
||||
lambda ind, h, l, c, v: ind.batch(c, v),
|
||||
),
|
||||
"WilliamsAD": (
|
||||
lambda: ta.WilliamsAD(),
|
||||
"ADOSC": (
|
||||
lambda: ta.ADOSC(),
|
||||
lambda ind, h, l, c, v: ind.batch(h, l, c),
|
||||
),
|
||||
"AnchoredVWAP": (
|
||||
@@ -1762,7 +1762,7 @@ def test_wad_reference():
|
||||
# TR_l = min(10, 8) = 8 -> delta = 12 - 8 = 4. AD = 4.
|
||||
# bar 2: prev=12, today high=11, low=7, close=7 (down day).
|
||||
# TR_h = max(12, 11) = 12 -> delta = 7 - 12 = -5. AD = 4 - 5 = -1.
|
||||
ad = ta.WilliamsAD()
|
||||
ad = ta.Wad()
|
||||
high = np.array([11.0, 13.0, 11.0])
|
||||
low = np.array([9.0, 8.0, 7.0])
|
||||
close = np.array([10.0, 12.0, 7.0])
|
||||
|
||||
@@ -3145,12 +3145,12 @@ impl WasmKvo {
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = WilliamsAD)]
|
||||
#[wasm_bindgen(js_name = ADOSC)]
|
||||
pub struct WasmAdOscillator {
|
||||
inner: wc::AdOscillator,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = WilliamsAD)]
|
||||
#[wasm_bindgen(js_class = ADOSC)]
|
||||
impl WasmAdOscillator {
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_without_default)]
|
||||
|
||||
Reference in New Issue
Block a user