feat: Family 01 Moving Averages — ALMA / McGinley / FRAMA / VIDYA / JMA / Alligator / EVWMA (#39)
* feat(alma): add Arnaud Legoux Moving Average
Gaussian-weighted moving average with configurable centre (offset in
[0, 1]) and kernel width (sigma > 0). Pre-computes normalised weights
at construction so each update is a single rolling window dot product.
Reference: Arnaud Legoux and Dimitrios Kouzis-Loukas, 2009.
Touchpoints:
- crates/wickra-core: alma.rs + mod.rs + lib.rs re-export
- bindings/python: PyAlma + __init__.py + test_new_indicators +
test_known_values reference
- bindings/node: AlmaNode + index.d.ts/index.js + indicators.test.js
factory + reference value
- bindings/wasm: wasm_scalar_indicator! macro
- fuzz: indicator_update target covers ALMA(9, 0.85, 6.0)
- crates/wickra/benches: bench_scalar entry
- README + CHANGELOG: Moving Averages row + Unreleased entry
* feat(mcginley): add McGinley Dynamic moving average
John McGinley's self-adjusting moving average with the recurrence
MD + (price - MD) / (0.6 * period * (price / MD)^4). Speeds up when
price falls below the indicator and damps when price runs above the
indicator. Seeded with the simple average of the first period inputs.
Reference: McGinley, Technical Analysis of Stocks & Commodities, 1990.
Touchpoints:
- crates/wickra-core: mcginley_dynamic.rs + mod.rs + lib.rs re-export
- bindings/python: PyMcGinleyDynamic + __init__.py + test_new_indicators
+ test_known_values reference
- bindings/node: McGinleyDynamicNode (scalar macro) + index.d.ts/index.js
+ indicators.test.js factory + reference value
- bindings/wasm: wasm_scalar_indicator! macro
- fuzz: indicator_update target covers McGinleyDynamic(10)
- crates/wickra/benches: bench_scalar entry
- README + CHANGELOG: Moving Averages row + Unreleased entry
* feat(frama): add Fractal Adaptive Moving Average
Ehlers' FRAMA adapts its smoothing constant to the fractal dimension of
the recent window: tight tracking in trends, heavy smoothing in chop.
Uses the close-only variant where max/min over each window half drive
the dimension estimate. Period must be even (default 16).
Reference: Ehlers, Fractal Adaptive Moving Average, 2005.
Touchpoints:
- crates/wickra-core: frama.rs + mod.rs + lib.rs re-export
- bindings/python: PyFrama + __init__.py + test_new_indicators +
test_known_values reference (constant series + uptrend tracking)
- bindings/node: FramaNode (scalar macro) + index.d.ts/index.js +
indicators.test.js factory + reference value
- bindings/wasm: wasm_scalar_indicator! macro
- fuzz: indicator_update target covers Frama(16)
- crates/wickra/benches: bench_scalar entry
- README + CHANGELOG: Moving Averages row + Unreleased entry
* feat(vidya): add Variable Index Dynamic Average
Chande's VIDYA — an EMA whose alpha scales with |CMO(cmo_period)| / 100.
Strong directional momentum lifts the smoothing constant toward the
EMA-of-period rate; flat or choppy windows shrink it toward zero so
VIDYA coasts on its previous value. Two parameters: period (14) and
cmo_period (9). Reuses the existing wickra-core Cmo internally.
Reference: Chande, Stocks & Commodities, 1992.
Also fixes a silent gap from d37fbd1 (feat(frama)): the PyFrama Python
class wrapper and its add_class registration were dropped because the
two edits hit "File has not been read yet" errors that scrolled past
in a batch. Adds them here alongside VIDYA's bindings.
Touchpoints (VIDYA): vidya.rs + mod.rs + lib.rs re-export, PyVidya +
__init__.py + test_new_indicators + test_known_values reference,
VidyaNode (manual two-param binding) + index.d.ts/index.js +
indicators.test.js factory + reference, wasm_scalar_indicator! macro,
fuzz target, bench, README + CHANGELOG.
* feat(jma): add Jurik Moving Average
Three-stage filter reconstruction of Mark Jurik's adaptive MA (the
algorithm is proprietary; this is the form used by most open-source
ports since the 1999 TASC article). Parameters: period (14), phase in
[-100, 100] (0), power in 1..=4 (2). State is seeded by setting
e0 = JMA = first input so a constant input stream is reproduced exactly.
Touchpoints: jma.rs + mod.rs + lib.rs re-export, PyJma + __init__.py +
test_new_indicators + test_known_values reference, JmaNode (manual
three-param binding) + index.d.ts/index.js + indicators.test.js factory
+ reference, wasm_scalar_indicator! macro, fuzz target, bench, README +
CHANGELOG.
* feat(alligator): add Bill Williams Alligator
Three SMMA lines (Jaw / Teeth / Lips) over the median price
(high + low) / 2 with default periods 13 / 8 / 5. Multi-output
indicator returning AlligatorOutput { jaw, teeth, lips }. The
original chart variant shifts each line forward for display; we
publish the unshifted SMMA values and leave the visual shift to
the consumer.
Reference: Bill Williams, Trading Chaos, 1995.
Touchpoints: alligator.rs + mod.rs + lib.rs re-export, PyAlligator
(Candle input, returns 3-tuple, ndarray (n, 3) batch) + __init__.py
+ test_new_indicators + test_known_values reference, AlligatorNode +
AlligatorValue + index.d.ts/index.js + indicators.test.js multi
factory + reference, WasmAlligator (manual JsValue object) +
candle-fuzz target + README + CHANGELOG.
* feat(evwma): add Elastic Volume-Weighted Moving Average
Christian P. Fries' elastic recurrence where the smoothing weight is the
bar's volume relative to the running window total:
V_sum_t = sum of volumes over the last period candles
EVWMA_t = ((V_sum_t - v_t) * EVWMA_{t-1} + v_t * close_t) / V_sum_t
A bar whose volume is small barely moves the average; a bar that
dominates the window pulls it strongly toward that bar's close. Seeded
with the close of the first full window; holds its previous value if
the entire window has zero volume.
Reference: Fries, Wilmott Magazine, 2001.
Touchpoints: evwma.rs + mod.rs + lib.rs re-export, PyEvwma (close +
volume batch) + __init__.py + test_new_indicators CANDLE_SCALAR +
test_known_values reference, EvwmaNode + index.d.ts/index.js +
indicators.test.js candleScalar factory + reference, WasmEvwma,
candle-fuzz target + README + CHANGELOG.
* ci: Force local wheel install in Python jobs
Use --no-index --no-deps so the Python matrix installs the freshly
built wheel from dist/ and never falls back to PyPI. Previously pip
sometimes picked the released 0.2.x wheel on macOS / Windows when its
platform tag was a wider match than the local build, which made the
job test the released package and miss any new symbols added in the
PR (e.g. AttributeError: module 'wickra' has no attribute 'ALMA').
numpy is already installed by the preceding pip step, so --no-deps
is safe.
This commit is contained in:
@@ -37,6 +37,11 @@ const scalarFactories = {
|
||||
ROC: () => new wickra.ROC(12),
|
||||
TRIX: () => new wickra.TRIX(9),
|
||||
KAMA: () => new wickra.KAMA(10, 2, 30),
|
||||
ALMA: () => new wickra.ALMA(9, 0.85, 6.0),
|
||||
McGinleyDynamic: () => new wickra.McGinleyDynamic(10),
|
||||
FRAMA: () => new wickra.FRAMA(16),
|
||||
VIDYA: () => new wickra.VIDYA(14, 9),
|
||||
JMA: () => new wickra.JMA(14, 0, 2),
|
||||
SMMA: () => new wickra.SMMA(14),
|
||||
TRIMA: () => new wickra.TRIMA(20),
|
||||
ZLEMA: () => new wickra.ZLEMA(14),
|
||||
@@ -86,6 +91,7 @@ const candleScalar = {
|
||||
AwesomeOscillator: { make: () => new wickra.AwesomeOscillator(5, 34), step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
|
||||
OBV: { make: () => new wickra.OBV(), step: (ind, i) => ind.update(close[i], volume[i]), batch: (ind) => ind.batch(close, volume) },
|
||||
VWMA: { make: () => new wickra.VWMA(20), step: (ind, i) => ind.update(close[i], volume[i]), batch: (ind) => ind.batch(close, volume) },
|
||||
EVWMA: { make: () => new wickra.EVWMA(20), step: (ind, i) => ind.update(close[i], volume[i]), batch: (ind) => ind.batch(close, volume) },
|
||||
UltimateOscillator: { make: () => new wickra.UltimateOscillator(7, 14, 28), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
AroonOscillator: { make: () => new wickra.AroonOscillator(14), step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
|
||||
NATR: { make: () => new wickra.NATR(14), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
@@ -122,6 +128,7 @@ for (const [name, d] of Object.entries(candleScalar)) {
|
||||
// --- Multi-output indicators: object update vs interleaved batch ---
|
||||
|
||||
const multi = {
|
||||
Alligator: { make: () => new wickra.Alligator(13, 8, 5), fields: ['jaw', 'teeth', 'lips'], step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
|
||||
MACD: { make: () => new wickra.MACD(12, 26, 9), fields: ['macd', 'signal', 'histogram'], step: (ind, i) => ind.update(close[i]), batch: (ind) => ind.batch(close) },
|
||||
BollingerBands: { make: () => new wickra.BollingerBands(20, 2), fields: ['upper', 'middle', 'lower', 'stddev'], step: (ind, i) => ind.update(close[i]), batch: (ind) => ind.batch(close) },
|
||||
Stochastic: { make: () => new wickra.Stochastic(14, 3), fields: ['k', 'd'], step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
@@ -258,3 +265,59 @@ test('LinRegAngle of a unit-slope series is 45 degrees', () => {
|
||||
const out = new wickra.LinRegAngle(5).batch([1, 2, 3, 4, 5, 6]);
|
||||
assert.ok(Math.abs(out[4] - 45) < 1e-9);
|
||||
});
|
||||
|
||||
test('EVWMA(2) reference values on [10, 20, 30] with volumes [1, 3, 1]', () => {
|
||||
const out = new wickra.EVWMA(2).batch([10, 20, 30], [1, 3, 1]);
|
||||
assert.ok(Number.isNaN(out[0]));
|
||||
assert.ok(Math.abs(out[1] - 20) < 1e-12);
|
||||
assert.ok(Math.abs(out[2] - 22.5) < 1e-12);
|
||||
});
|
||||
|
||||
test('Alligator on a flat median price seeds to that median', () => {
|
||||
const n = 30;
|
||||
const out = new wickra.Alligator(13, 8, 5).batch(Array(n).fill(11), Array(n).fill(9));
|
||||
// All three SMMAs see median (11 + 9) / 2 = 10 every bar.
|
||||
for (let i = 12; i < n; i++) {
|
||||
assert.ok(Math.abs(out[i * 3] - 10) < 1e-12, `jaw at ${i}: ${out[i * 3]}`);
|
||||
assert.ok(Math.abs(out[i * 3 + 1] - 10) < 1e-12);
|
||||
assert.ok(Math.abs(out[i * 3 + 2] - 10) < 1e-12);
|
||||
}
|
||||
});
|
||||
|
||||
test('JMA on a flat series reproduces the constant', () => {
|
||||
const out = new wickra.JMA(14, 0, 2).batch(Array(30).fill(42));
|
||||
for (let i = 0; i < 30; i++) assert.ok(Math.abs(out[i] - 42) < 1e-12);
|
||||
});
|
||||
|
||||
test('VIDYA on a flat series holds the seed', () => {
|
||||
const out = new wickra.VIDYA(14, 4).batch(Array(20).fill(42));
|
||||
for (let i = 0; i < 4; i++) assert.ok(Number.isNaN(out[i]));
|
||||
for (let i = 4; i < 20; i++) assert.ok(Math.abs(out[i] - 42) < 1e-12);
|
||||
});
|
||||
|
||||
test('FRAMA pure uptrend hugs the latest close', () => {
|
||||
const out = new wickra.FRAMA(4).batch([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
assert.ok(Math.abs(out[out.length - 1] - 8) < 0.05);
|
||||
});
|
||||
|
||||
test('McGinleyDynamic(3) seeds with SMA and recurses on the next price', () => {
|
||||
// Seed = SMA([10, 20, 30]) = 20. On 40: ratio = 2, divisor = 0.6*3*16 = 28.8.
|
||||
const out = new wickra.McGinleyDynamic(3).batch([10, 20, 30, 40]);
|
||||
assert.ok(Number.isNaN(out[0]) && Number.isNaN(out[1]));
|
||||
assert.ok(Math.abs(out[2] - 20) < 1e-12);
|
||||
const expected = 20 + 20 / (0.6 * 3 * 16);
|
||||
assert.ok(Math.abs(out[3] - expected) < 1e-12);
|
||||
});
|
||||
|
||||
test('ALMA(3, 0.85, 6) reference value on [10, 20, 30]', () => {
|
||||
// m = 0.85 * 2 = 1.7; s = 3 / 6 = 0.5; 2*s^2 = 0.5.
|
||||
const out = new wickra.ALMA(3, 0.85, 6).batch([10, 20, 30]);
|
||||
assert.ok(Number.isNaN(out[0]) && Number.isNaN(out[1]));
|
||||
const w = [0, 1, 2].map((i) => Math.exp(-Math.pow(i - 1.7, 2) / 0.5));
|
||||
const s = w[0] + w[1] + w[2];
|
||||
const expected = (10 * w[0] + 20 * w[1] + 30 * w[2]) / s;
|
||||
assert.ok(Math.abs(out[2] - expected) < 1e-12);
|
||||
// The heavy offset toward the newest sample lifts the average above the
|
||||
// simple mean of 20.
|
||||
assert.ok(out[2] > 20);
|
||||
});
|
||||
|
||||
@@ -310,7 +310,7 @@ if (!nativeBinding) {
|
||||
throw new Error(`Failed to load native binding`)
|
||||
}
|
||||
|
||||
const { version, SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, SMMA, TRIMA, ZLEMA, MOM, CMO, DPO, StdDev, UlcerIndex, VerticalHorizontalFilter, ZScore, MACD, BollingerBands, ATR, Stochastic, OBV, ADX, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, RollingVWAP, AwesomeOscillator, Aroon, KAMA, T3, TSI, PMO, ADL, VolumePriceTrend, ChaikinMoneyFlow, ChaikinOscillator, ForceIndex, EaseOfMovement, SuperTrend, ChandelierExit, ChandeKrollStop, AtrTrailingStop, TypicalPrice, MedianPrice, WeightedClose, LinearRegression, LinRegSlope, AcceleratorOscillator, BalanceOfPower, ChoppinessIndex, TrueRange, ChaikinVolatility, LinRegAngle, BollingerBandwidth, PercentB, NATR, HistoricalVolatility, AroonOscillator, Vortex, MassIndex, StochRSI, UltimateOscillator, PPO, Coppock, VWMA } = nativeBinding
|
||||
const { version, SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, SMMA, TRIMA, ZLEMA, MOM, CMO, DPO, StdDev, UlcerIndex, VerticalHorizontalFilter, ZScore, MACD, BollingerBands, ATR, Stochastic, OBV, ADX, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, RollingVWAP, AwesomeOscillator, Aroon, KAMA, ALMA, McGinleyDynamic, FRAMA, VIDYA, JMA, Alligator, EVWMA, T3, TSI, PMO, ADL, VolumePriceTrend, ChaikinMoneyFlow, ChaikinOscillator, ForceIndex, EaseOfMovement, SuperTrend, ChandelierExit, ChandeKrollStop, AtrTrailingStop, TypicalPrice, MedianPrice, WeightedClose, LinearRegression, LinRegSlope, AcceleratorOscillator, BalanceOfPower, ChoppinessIndex, TrueRange, ChaikinVolatility, LinRegAngle, BollingerBandwidth, PercentB, NATR, HistoricalVolatility, AroonOscillator, Vortex, MassIndex, StochRSI, UltimateOscillator, PPO, Coppock, VWMA } = nativeBinding
|
||||
|
||||
module.exports.version = version
|
||||
module.exports.SMA = SMA
|
||||
@@ -349,6 +349,13 @@ module.exports.RollingVWAP = RollingVWAP
|
||||
module.exports.AwesomeOscillator = AwesomeOscillator
|
||||
module.exports.Aroon = Aroon
|
||||
module.exports.KAMA = KAMA
|
||||
module.exports.ALMA = ALMA
|
||||
module.exports.McGinleyDynamic = McGinleyDynamic
|
||||
module.exports.FRAMA = FRAMA
|
||||
module.exports.VIDYA = VIDYA
|
||||
module.exports.JMA = JMA
|
||||
module.exports.Alligator = Alligator
|
||||
module.exports.EVWMA = EVWMA
|
||||
module.exports.T3 = T3
|
||||
module.exports.TSI = TSI
|
||||
module.exports.PMO = PMO
|
||||
|
||||
@@ -116,6 +116,8 @@ node_scalar_indicator!(
|
||||
wc::VerticalHorizontalFilter
|
||||
);
|
||||
node_scalar_indicator!(ZScoreNode, "ZScore", wc::ZScore);
|
||||
node_scalar_indicator!(McGinleyDynamicNode, "McGinleyDynamic", wc::McGinleyDynamic);
|
||||
node_scalar_indicator!(FramaNode, "FRAMA", wc::Frama);
|
||||
|
||||
// ============================== MACD ==============================
|
||||
|
||||
@@ -1103,6 +1105,229 @@ impl KamaNode {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== EVWMA ==============================
|
||||
|
||||
#[napi(js_name = "EVWMA")]
|
||||
pub struct EvwmaNode {
|
||||
inner: wc::Evwma,
|
||||
}
|
||||
#[napi]
|
||||
impl EvwmaNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::Evwma::new(clamp_period(period)).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, close: f64, volume: f64) -> napi::Result<Option<f64>> {
|
||||
Ok(self.inner.update(cnd(close, close, close, volume)?))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(&mut self, close: Vec<f64>, volume: Vec<f64>) -> napi::Result<Vec<f64>> {
|
||||
if close.len() != volume.len() {
|
||||
return Err(NapiError::from_reason(
|
||||
"close and volume must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::with_capacity(close.len());
|
||||
for i in 0..close.len() {
|
||||
out.push(
|
||||
self.inner
|
||||
.update(cnd(close[i], close[i], close[i], volume[i])?)
|
||||
.unwrap_or(f64::NAN),
|
||||
);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
#[napi(js_name = "isReady")]
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.inner.is_ready()
|
||||
}
|
||||
#[napi(js_name = "warmupPeriod")]
|
||||
pub fn warmup_period(&self) -> u32 {
|
||||
self.inner.warmup_period() as u32
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Alligator ==============================
|
||||
|
||||
#[napi(object)]
|
||||
pub struct AlligatorValue {
|
||||
pub jaw: f64,
|
||||
pub teeth: f64,
|
||||
pub lips: f64,
|
||||
}
|
||||
|
||||
#[napi(js_name = "Alligator")]
|
||||
pub struct AlligatorNode {
|
||||
inner: wc::Alligator,
|
||||
}
|
||||
#[napi]
|
||||
impl AlligatorNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(jaw: u32, teeth: u32, lips: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::Alligator::new(clamp_period(jaw), clamp_period(teeth), clamp_period(lips))
|
||||
.map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
#[napi(js_name = "isReady")]
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.inner.is_ready()
|
||||
}
|
||||
#[napi(js_name = "warmupPeriod")]
|
||||
pub fn warmup_period(&self) -> u32 {
|
||||
self.inner.warmup_period() as u32
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<AlligatorValue>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.update(cnd(high, low, low, 0.0)?)
|
||||
.map(|o| AlligatorValue {
|
||||
jaw: o.jaw,
|
||||
teeth: o.teeth,
|
||||
lips: o.lips,
|
||||
}))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(&mut self, high: Vec<f64>, low: Vec<f64>) -> napi::Result<Vec<f64>> {
|
||||
if high.len() != low.len() {
|
||||
return Err(NapiError::from_reason(
|
||||
"high and low must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let n = high.len();
|
||||
let mut out = vec![f64::NAN; n * 3];
|
||||
for i in 0..n {
|
||||
if let Some(o) = self.inner.update(cnd(high[i], low[i], low[i], 0.0)?) {
|
||||
out[i * 3] = o.jaw;
|
||||
out[i * 3 + 1] = o.teeth;
|
||||
out[i * 3 + 2] = o.lips;
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== JMA ==============================
|
||||
|
||||
#[napi(js_name = "JMA")]
|
||||
pub struct JmaNode {
|
||||
inner: wc::Jma,
|
||||
}
|
||||
#[napi]
|
||||
impl JmaNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32, phase: f64, power: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::Jma::new(clamp_period(period), phase, power).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
#[napi(js_name = "isReady")]
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.inner.is_ready()
|
||||
}
|
||||
#[napi(js_name = "warmupPeriod")]
|
||||
pub fn warmup_period(&self) -> u32 {
|
||||
self.inner.warmup_period() as u32
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, value: f64) -> Option<f64> {
|
||||
self.inner.update(value)
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(&mut self, prices: Vec<f64>) -> Vec<f64> {
|
||||
flatten(self.inner.batch(&prices))
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== VIDYA ==============================
|
||||
|
||||
#[napi(js_name = "VIDYA")]
|
||||
pub struct VidyaNode {
|
||||
inner: wc::Vidya,
|
||||
}
|
||||
#[napi]
|
||||
impl VidyaNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32, cmo_period: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::Vidya::new(clamp_period(period), clamp_period(cmo_period))
|
||||
.map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
#[napi(js_name = "isReady")]
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.inner.is_ready()
|
||||
}
|
||||
#[napi(js_name = "warmupPeriod")]
|
||||
pub fn warmup_period(&self) -> u32 {
|
||||
self.inner.warmup_period() as u32
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, value: f64) -> Option<f64> {
|
||||
self.inner.update(value)
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(&mut self, prices: Vec<f64>) -> Vec<f64> {
|
||||
flatten(self.inner.batch(&prices))
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== ALMA ==============================
|
||||
|
||||
#[napi(js_name = "ALMA")]
|
||||
pub struct AlmaNode {
|
||||
inner: wc::Alma,
|
||||
}
|
||||
#[napi]
|
||||
impl AlmaNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32, offset: f64, sigma: f64) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::Alma::new(clamp_period(period), offset, sigma).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
#[napi(js_name = "isReady")]
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.inner.is_ready()
|
||||
}
|
||||
#[napi(js_name = "warmupPeriod")]
|
||||
pub fn warmup_period(&self) -> u32 {
|
||||
self.inner.warmup_period() as u32
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, value: f64) -> Option<f64> {
|
||||
self.inner.update(value)
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(&mut self, prices: Vec<f64>) -> Vec<f64> {
|
||||
flatten(self.inner.batch(&prices))
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== T3 ==============================
|
||||
|
||||
#[napi(js_name = "T3")]
|
||||
|
||||
Reference in New Issue
Block a user