feat(core): B4 price oscillators (TsfOscillator, MacdHistogram, PpoHistogram) (#184)
Adds three **Price Oscillators** family indicators (420 → 423). ## Indicators - **TsfOscillator** — `100·(close − TSF)/close`, the percentage gap of the close to the **one-bar-ahead** time-series forecast. Close-relative companion to `Cfo`, which measures the same gap against the regression value at the *current* bar; the two differ by exactly the slope term `100·b/close`. - **MacdHistogram** — the standalone `macd − signal` bar of MACD exposed as a plain `f64` series. - **PpoHistogram** — the Percentage Price Oscillator with its 9-period signal EMA and the resulting scale-free, zero-centered histogram (PPO itself only emits the line). All three are scalar `f64` indicators wrapping existing, already-tested building blocks (`MacdIndicator`, `Ppo` + `Ema`, `Tsf`). ## Scope notes (VORAB-CHECK) The B4 roadmap listed six items; three were dropped to avoid duplicates: - *Forecast Oscillator* already ships as `Cfo`. - *Derivative Oscillator* already ships (`DerivativeOscillator`, B2). - *Detrended Synthetic Price* deferred — no citable formula distinct from the existing `Apo`/`Dpo`. ## Touchpoints Core (`tsf_oscillator.rs`, `macd_histogram.rs`, `ppo_histogram.rs`) with full per-branch unit tests, `mod.rs`/`lib.rs`, python/node/wasm bindings (wasm via typed-arg macro, python/node hand-written for the multi-arg histograms), fuzz drivers, python reference + streaming-vs-batch tests, node factories, README family row + counter, CHANGELOG. Local verify: `cargo test --workspace` green, `clippy -D warnings` clean, node 498 tests, full python suite green.
This commit is contained in:
@@ -45,6 +45,9 @@ def ohlcv() -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
||||
# --- Scalar (f64 -> f64) indicators ---------------------------------------
|
||||
|
||||
SCALAR = [
|
||||
(ta.PpoHistogram, (3, 6, 3)),
|
||||
(ta.MacdHistogram, (3, 6, 3)),
|
||||
(ta.TsfOscillator, (3,)),
|
||||
(ta.WAVE_PM, (32, 3)),
|
||||
(ta.POLARIZED_FRACTAL_EFFICIENCY, (10, 5)),
|
||||
(ta.TREND_STRENGTH_INDEX, (20,)),
|
||||
@@ -2862,6 +2865,31 @@ def test_kase_permission_stochastic_reference():
|
||||
assert out[-1][0] == pytest.approx(50.0)
|
||||
assert out[-1][1] == pytest.approx(50.0)
|
||||
|
||||
|
||||
def test_tsf_oscillator_reference():
|
||||
t = ta.TsfOscillator(3)
|
||||
assert t.update(1.0) is None
|
||||
assert t.update(2.0) is None
|
||||
assert t.update(9.0) == pytest.approx(-33.33333333333333)
|
||||
|
||||
|
||||
def test_macd_histogram_reference():
|
||||
# On a constant-slope ramp the MACD line is flat once seeded, so the
|
||||
# signal EMA catches up and the histogram collapses to 0.
|
||||
t = ta.MacdHistogram(3, 6, 3)
|
||||
for i in range(7):
|
||||
assert t.update(100.0 + i * 2.0) is None
|
||||
assert t.update(100.0 + 7 * 2.0) == pytest.approx(0.0, abs=1e-9)
|
||||
|
||||
|
||||
def test_ppo_histogram_reference():
|
||||
# PPO divides the EMA gap by the slow EMA, so on the same ramp the ratio
|
||||
# keeps drifting and the histogram stays non-zero.
|
||||
t = ta.PpoHistogram(3, 6, 3)
|
||||
for i in range(7):
|
||||
assert t.update(100.0 + i * 2.0) is None
|
||||
assert t.update(100.0 + 7 * 2.0) == pytest.approx(-0.052098, abs=1e-6)
|
||||
|
||||
# --- Lifecycle ------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user