Normalized Resonator

Parameters: period = 100 | delta = 0.5 | lookback_mult = 1 | signal_length = 9

Overview

Normalized Resonator runs a two-pole band-pass style filter over the selected series, then divides that response by the strongest absolute resonance seen in a rolling peak window. The resulting oscillator stays bounded and can be compared more cleanly across changing volatility regimes.

A short EMA of the normalized oscillator becomes the signal line. The default candle source is hl2, the stream requires two valid samples before it emits values, and any non-finite update resets the internal state.

Defaults: `period = 100`, `delta = 0.5`, `lookback_mult = 1.0`, and `signal_length = 9`.

Implementation Examples

Use the default hl2 candle source or pass a custom numeric slice.

use vector_ta::indicators::normalized_resonator::{
    normalized_resonator,
    NormalizedResonatorInput,
    NormalizedResonatorParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

let output = normalized_resonator(&NormalizedResonatorInput::from_slice(
    &data,
    NormalizedResonatorParams {
        period: Some(100),
        delta: Some(0.5),
        lookback_mult: Some(1.0),
        signal_length: Some(9),
    },
))?;

let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let candle_output = normalized_resonator(
    &NormalizedResonatorInput::with_default_candles(&candles),
)?;

println!("oscillator = {:?}", output.oscillator.last());
println!("signal = {:?}", candle_output.signal.last());

API Reference

Input Methods
NormalizedResonatorInput::from_candles(
    &Candles,
    "hl2",
    NormalizedResonatorParams,
) -> NormalizedResonatorInput

NormalizedResonatorInput::from_slice(&[f64], NormalizedResonatorParams)
    -> NormalizedResonatorInput

NormalizedResonatorInput::with_default_candles(&Candles)
    -> NormalizedResonatorInput
Parameters Structure
pub struct NormalizedResonatorParams {
    pub period: Option<usize>,        // default 100
    pub delta: Option<f64>,           // default 0.5
    pub lookback_mult: Option<f64>,   // default 1.0
    pub signal_length: Option<usize>, // default 9
}
Output Structure
pub struct NormalizedResonatorOutput {
    pub oscillator: Vec<f64>,
    pub signal: Vec<f64>,
}
Validation, Warmup & NaNs
  • The slice must not be empty and must contain at least one finite value.
  • period must be at least 2.
  • delta must be finite and stay within (0, 1].
  • lookback_mult must be finite and greater than 0.
  • signal_length must be greater than 0.
  • The longest consecutive finite run must contain at least 3 samples, and the stream reports get_warmup_period() -> 2.
  • Any non-finite streaming sample resets the internal state and returns None.
  • Batch mode rejects invalid range tuples and non-batch kernels.
Builder, Streaming & Batch APIs
NormalizedResonatorBuilder::new()
    .period(usize)
    .delta(f64)
    .lookback_mult(f64)
    .signal_length(usize)
    .kernel(Kernel)
    .apply(&Candles, "hl2")
    .apply_slice(&[f64])
    .into_stream()

NormalizedResonatorStream::try_new(params)
stream.update(f64) -> Option<(f64, f64)>
stream.reset()
stream.get_warmup_period() -> usize

NormalizedResonatorBatchBuilder::new()
    .period_range(start, end, step)
    .delta_range(start, end, step)
    .lookback_mult_range(start, end, step)
    .signal_length_range(start, end, step)
    .apply_slice(&[f64])
    .apply_candles(&Candles, "hl2")

Python Bindings

Python exposes a scalar function, a stream class, and a batch helper. The scalar call returns oscillator and signal arrays, the stream yields one tuple per update after warmup, and the batch helper returns a dictionary with reshaped matrices plus the parameter axes that were swept.

from vector_ta import (
    normalized_resonator,
    normalized_resonator_batch,
    NormalizedResonatorStream,
)

oscillator, signal = normalized_resonator(
    data,
    period=100,
    delta=0.5,
    lookback_mult=1.0,
    signal_length=9,
)

stream = NormalizedResonatorStream(100, 0.5, 1.0, 9)
point = stream.update(data[-1])

batch = normalized_resonator_batch(
    data,
    period_range=(60, 100, 20),
    delta_range=(0.3, 0.5, 0.1),
    lookback_mult_range=(1.0, 2.0, 0.5),
    signal_length_range=(5, 9, 2),
)

print(batch.keys())
# dict_keys(['oscillator', 'signal', 'periods', 'deltas',
#            'lookback_multipliers', 'signal_lengths', 'rows', 'cols'])

JavaScript/WASM Bindings

The WASM surface includes a high-level scalar function, a batch function, and low-level allocation and into-buffer helpers. The scalar and batch calls return plain objects, while the low-level exports let callers reuse memory for oscillator and signal arrays.

import init, {
  normalized_resonator_js,
  normalized_resonator_batch_js,
  normalized_resonator_alloc,
  normalized_resonator_free,
  normalized_resonator_into,
  normalized_resonator_batch_into,
} from "vector-ta-wasm";

await init();

const out = normalized_resonator_js(data, 100, 0.5, 1.0, 9);
const batch = normalized_resonator_batch_js(data, {
  period_range: [60, 100, 20],
  delta_range: [0.3, 0.5, 0.1],
  lookback_mult_range: [1.0, 2.0, 0.5],
  signal_length_range: [5, 9, 2],
});

CUDA Bindings (Rust)

Additional details for the CUDA bindings can be found inside the VectorTA repository.

Performance Analysis

Comparison:
View:
Placeholder data (no recorded benchmarks for this indicator)

Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)

Related Indicators