L2 Ehlers Signal To Noise

Parameters: source = hl2 | smooth_period = 10

Overview

L2 Ehlers Signal To Noise produces a single signal-to-noise series from a price source and the bar range. The implementation smooths high-low range into a noise estimate, builds Ehlers-style smoothed, detrended, in-phase, and quadrature components from the selected source, estimates dominant cycle period, and then computes a decibel-like power-to-noise ratio before recursively smoothing the final values series.

Defaults: candle source = "hl2" and smooth_period = 10. The Rust path needs the chosen source plus high and low.

Implementation Examples

Build the indicator from candles or explicit source/high/low slices:

use vector_ta::indicators::l2_ehlers_signal_to_noise::{
    l2_ehlers_signal_to_noise,
    L2EhlersSignalToNoiseInput,
    L2EhlersSignalToNoiseParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

let source = vec![100.0, 100.8, 101.2, 101.0, 102.1, 102.9, 103.2];
let high = vec![100.6, 101.1, 101.8, 101.5, 102.8, 103.4, 103.8];
let low = vec![99.6, 100.3, 100.7, 100.5, 101.4, 102.1, 102.7];

let input = L2EhlersSignalToNoiseInput::from_slices(
    &source,
    &high,
    &low,
    L2EhlersSignalToNoiseParams::default(),
);
let out = l2_ehlers_signal_to_noise(&input)?;

// Candles use a selectable source field, defaulting to hl2
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let candle_input = L2EhlersSignalToNoiseInput::with_default_candles(&candles);
let candle_out = l2_ehlers_signal_to_noise(&candle_input)?;

println!("{:?}", out.values.last());

API Reference

Input Methods
// From candles with selectable source
L2EhlersSignalToNoiseInput::from_candles(
    &Candles,
    &str,
    L2EhlersSignalToNoiseParams,
) -> L2EhlersSignalToNoiseInput

// From slices
L2EhlersSignalToNoiseInput::from_slices(
    &[f64],
    &[f64],
    &[f64],
    L2EhlersSignalToNoiseParams,
) -> L2EhlersSignalToNoiseInput

// Default candles source is "hl2"
L2EhlersSignalToNoiseInput::with_default_candles(&Candles)
    -> L2EhlersSignalToNoiseInput
Parameters and Output
pub struct L2EhlersSignalToNoiseParams {
    pub smooth_period: Option<usize>, // default 10
}

pub struct L2EhlersSignalToNoiseOutput {
    pub values: Vec<f64>,
}
Builder, Stream, and Batch Types
L2EhlersSignalToNoiseBuilder::new()
    .source(&'static str)
    .smooth_period(usize)
    .kernel(Kernel)
    .apply(&Candles) -> Result<L2EhlersSignalToNoiseOutput, _>

L2EhlersSignalToNoiseBuilder::new()
    .apply_slices(&[f64], &[f64], &[f64]) -> Result<L2EhlersSignalToNoiseOutput, _>

L2EhlersSignalToNoiseBuilder::new()
    .into_stream() -> Result<L2EhlersSignalToNoiseStream, _>

L2EhlersSignalToNoiseStream::update(source, high, low) -> f64

L2EhlersSignalToNoiseBatchBuilder::new()
    .source(&'static str)
    .smooth_period_range((usize, usize, usize))
    .kernel(Kernel)
    .apply(&Candles) -> Result<L2EhlersSignalToNoiseBatchOutput, _>

L2EhlersSignalToNoiseBatchBuilder::new()
    .apply_slices(&[f64], &[f64], &[f64]) -> Result<L2EhlersSignalToNoiseBatchOutput, _>
Warmup, NaNs, and Validation
  • Inputs must be non-empty equal-length source, high, and low arrays.
  • The first valid bar requires all three values to be finite. If none are finite, the indicator returns AllValuesNaN.
  • smooth_period must be non-zero.
  • The batch and scalar validators require at least MIN_WARMUP_BARS + 1 valid bars after the first valid triple, so at least 7 valid bars.
  • The scalar path allocates a NaN prefix of first_valid + 6 bars.
  • The stream returns a plain f64, not Option. Invalid inputs and warmup bars yield NaN.
  • The low-level core also returns NaN until valid_count > MIN_WARMUP_BARS.
Calculation Notes and Batch Output
  • range_1 is recursively smoothed as 0.1 * (high - low) + 0.9 * previous_range_1.
  • The core uses a 4-bar weighted source smoother, 7-slot rings for detrender / I / Q components, and a period multiplier of 0.075 * smooth_period + 0.54.
  • Dominant cycle period is clamped to [6.0, 50.0] and then recursively smoothed.
  • The final signal-to-noise estimate uses 10 * log10(power / noise) + 6 and is recursively smoothed as 0.25 * raw + 0.75 * prev.
  • Batch output is `L2EhlersSignalToNoiseBatchOutput` with `values`, `combos`, `rows`, and `cols`, and the values buffer is flattened row-major.
  • Batch range is only smooth_period: (start, end, step). When step == 0, start must equal end.
Error Handling
use vector_ta::indicators::l2_ehlers_signal_to_noise::L2EhlersSignalToNoiseError;

// Common errors:
// - EmptyInputData
// - AllValuesNaN
// - InconsistentSliceLengths
// - InvalidSmoothPeriod
// - NotEnoughValidData
// - OutputLengthMismatch
// - InvalidRange
// - InvalidKernelForBatch

Python Bindings

Basic Usage

The Python scalar function returns one NumPy array:

import numpy as np
from vector_ta import l2_ehlers_signal_to_noise

values = l2_ehlers_signal_to_noise(
    source,
    high,
    low,
    smooth_period=10,
    kernel="auto",
)

print(values.shape)
Streaming Real-time Updates

The Python stream wrapper also returns a raw float each update:

from vector_ta import L2EhlersSignalToNoiseStream

stream = L2EhlersSignalToNoiseStream(smooth_period=10)

for src, hi, lo in live_points:
    value = stream.update(src, hi, lo)
    if np.isfinite(value):
        print(value)
Batch Processing

Batch output keys are values, smooth_periods, rows, and cols:

from vector_ta import l2_ehlers_signal_to_noise_batch

result = l2_ehlers_signal_to_noise_batch(
    source,
    high,
    low,
    smooth_period_range=(10, 20, 5),
    kernel="auto",
)

values = result["values"]
smooth_periods = result["smooth_periods"]
rows = result["rows"]
cols = result["cols"]

JavaScript/WASM Bindings

Basic Usage

The scalar WASM export returns a single Float64Array:

import { l2_ehlers_signal_to_noise_js } from 'vectorta-wasm';

const values = l2_ehlers_signal_to_noise_js(source, high, low, 10);
console.log(values);
Host Buffer API

The low-level API writes one output array of length len:

import {
  l2_ehlers_signal_to_noise_alloc,
  l2_ehlers_signal_to_noise_free,
  l2_ehlers_signal_to_noise_into,
} from 'vectorta-wasm';

const ptr = l2_ehlers_signal_to_noise_alloc(source.length);
try {
  l2_ehlers_signal_to_noise_into(sourcePtr, highPtr, lowPtr, ptr, source.length, 10);
} finally {
  l2_ehlers_signal_to_noise_free(ptr, source.length);
}
Batch Processing

The JS batch config must provide smooth_period_range as a 3-element array:

import { l2_ehlers_signal_to_noise_batch_js } from 'vectorta-wasm';

const batch = l2_ehlers_signal_to_noise_batch_js(source, high, low, {
  smooth_period_range: [10, 20, 5],
}) as {
  values: number[];
  combos: Array<{ smooth_period?: number }>;
  rows: number;
  cols: number;
};

console.log(batch.rows, batch.cols);
console.log(batch.combos);

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