feat(apo): add Absolute Price Oscillator
EMA(close, fast) - EMA(close, slow). Like MACD without the signal EMA. Defaults to (fast = 12, slow = 26); fast must be strictly less than slow. Touchpoints: apo.rs + mod.rs + lib.rs re-export, PyApo + __init__.py + test_new_indicators SCALAR + test_known_values flat reference, ApoNode + index.d.ts/index.js + indicators.test.js factory + reference, WasmApo via scalar macro, scalar-fuzz target, README + CHANGELOG.
This commit is contained in:
@@ -56,6 +56,7 @@ from ._wickra import (
|
||||
PMO,
|
||||
StochRSI,
|
||||
UltimateOscillator,
|
||||
APO,
|
||||
PPO,
|
||||
DPO,
|
||||
Coppock,
|
||||
@@ -137,6 +138,7 @@ __all__ = [
|
||||
"PMO",
|
||||
"StochRSI",
|
||||
"UltimateOscillator",
|
||||
"APO",
|
||||
"PPO",
|
||||
"DPO",
|
||||
"Coppock",
|
||||
|
||||
@@ -66,6 +66,13 @@ def test_rsi_wilder_textbook_first_value():
|
||||
assert math.isclose(out[14], 70.464, abs_tol=0.05)
|
||||
|
||||
|
||||
def test_apo_constant_series_converges_to_zero():
|
||||
# Both EMAs reproduce a constant exactly, so APO = 0 after warmup.
|
||||
out = ta.APO(3, 5).batch(np.full(30, 42.0, dtype=np.float64))
|
||||
assert np.all(np.isnan(out[:4]))
|
||||
np.testing.assert_allclose(out[4:], 0.0, atol=1e-12)
|
||||
|
||||
|
||||
def test_macd_constant_series_converges_to_zero():
|
||||
out = ta.MACD().batch(np.full(200, 100.0))
|
||||
# Last row's MACD and signal must be ~0.
|
||||
|
||||
@@ -51,6 +51,7 @@ SCALAR = [
|
||||
(ta.PMO, (35, 20)),
|
||||
(ta.StochRSI, (14, 14)),
|
||||
(ta.PPO, (12, 26)),
|
||||
(ta.APO, (12, 26)),
|
||||
(ta.DPO, (20,)),
|
||||
(ta.Coppock, (14, 11, 10)),
|
||||
(ta.StdDev, (20,)),
|
||||
|
||||
Reference in New Issue
Block a user