L1 Ehlers Phasor
domestic_cycle_length = 15 Overview
L1 Ehlers Phasor computes a rolling real and imaginary phasor over a fixed domestic-cycle window, then converts those components into a phase angle in degrees. The implementation normalizes that phase angle into the 0 to 360 degree range after quadrant adjustment and a 90 degree offset.
Candle input always uses close, and there is no candle-source selector for this indicator. Warmup lasts until a full valid window is available, and any non-finite values inside the active window force the output back to NaN until that invalid value rolls out of the ring buffer.
Defaults: domestic_cycle_length = 15, candle input uses close.
Implementation Examples
Calculate the phase-angle series from a raw slice or directly from candle closes:
use vector_ta::indicators::l1_ehlers_phasor::{
l1_ehlers_phasor, L1EhlersPhasorInput, L1EhlersPhasorParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};
let data = vec![100.0, 101.0, 102.5, 101.8, 103.1, 104.4, 103.7, 105.2];
let input = L1EhlersPhasorInput::from_slice(
&data,
L1EhlersPhasorParams {
domestic_cycle_length: Some(15),
},
);
let out = l1_ehlers_phasor(&input)?;
println!("{:?}", out.values);
// Candle input always uses candles.close
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let out = l1_ehlers_phasor(&L1EhlersPhasorInput::with_default_candles(&candles))?; API Reference
Input Methods ▼
L1EhlersPhasorInput::from_candles(&Candles, L1EhlersPhasorParams) -> L1EhlersPhasorInput
L1EhlersPhasorInput::from_slice(&[f64], L1EhlersPhasorParams) -> L1EhlersPhasorInput
L1EhlersPhasorInput::with_default_candles(&Candles) -> L1EhlersPhasorInput Parameters Structure ▼
pub struct L1EhlersPhasorParams {
pub domestic_cycle_length: Option<usize>,
}
// Default
// domestic_cycle_length = 15 Output Structure ▼
pub struct L1EhlersPhasorOutput {
pub values: Vec<f64>,
}
// values contain the normalized phase angle in degrees. Builders, Batch Range, and Stream ▼
L1EhlersPhasorBuilder::new()exposesdomestic_cycle_length(),kernel(),apply(),apply_slice(), andinto_stream().L1EhlersPhasorStream::update(value)returnsf64, notOption<f64>. Warmup and invalid windows produceNaN.L1EhlersPhasorBatchRangecontains onlydomestic_cycle_length: (start, end, step).L1EhlersPhasorBatchBuilder::new()exposesdomestic_cycle_length_range(),kernel(),apply(), andapply_slice().- Batch output is
L1EhlersPhasorBatchOutputwithvalues,combos,rows, andcols;valuesis flattened row-major.
Validation, Warmup & NaNs ▼
- Input must be non-empty and contain at least one finite value; otherwise the indicator returns
EmptyInputDataorAllValuesNaN. domestic_cycle_lengthmust be greater than zero.- Single-run validation requires at least
domestic_cycle_lengthvalues from the first finite sample to the end of the slice; otherwise the indicator returnsNotEnoughValidData. - Single-output warmup fills indices before
first_valid + domestic_cycle_length - 1withNaN. - Inside the core stream, any non-finite value inside the active ring buffer makes the output
NaNuntil the window is fully valid again. - Batch range expansion only supports a single static value when
step = 0; otherwise it requires an ascending inclusive range.
Error Handling ▼
use vector_ta::indicators::l1_ehlers_phasor::L1EhlersPhasorError;
match l1_ehlers_phasor(&input) {
Ok(out) => println!("{:?}", out.values),
Err(L1EhlersPhasorError::EmptyInputData) =>
eprintln!("input data cannot be empty"),
Err(L1EhlersPhasorError::AllValuesNaN) =>
eprintln!("all input values were invalid"),
Err(L1EhlersPhasorError::InvalidDomesticCycleLength { domestic_cycle_length }) =>
eprintln!("invalid domestic_cycle_length: {}", domestic_cycle_length),
Err(L1EhlersPhasorError::NotEnoughValidData { needed, valid }) =>
eprintln!("need {} valid samples, only {} available", needed, valid),
Err(L1EhlersPhasorError::InvalidKernelForBatch(kernel)) =>
eprintln!("invalid batch kernel: {:?}", kernel),
Err(e) => eprintln!("L1 Ehlers Phasor error: {}", e),
} Python Bindings
Basic Usage ▼
Python exposes l1_ehlers_phasor(data, domestic_cycle_length=15, kernel=None) and returns a NumPy array of phase angles.
import numpy as np
from vector_ta import l1_ehlers_phasor
data = np.array([100.0, 101.0, 102.5, 101.8, 103.1, 104.4], dtype=np.float64)
values = l1_ehlers_phasor(data, domestic_cycle_length=15) Streaming Real-time Updates ▼
The Python stream class is L1EhlersPhasorStream. Its update(value) method returns a raw float and may emit nan during warmup.
from vector_ta import L1EhlersPhasorStream
stream = L1EhlersPhasorStream(domestic_cycle_length=15)
for value in feed:
phase_angle = stream.update(value)
print(phase_angle) Batch Processing ▼
l1_ehlers_phasor_batch returns a dict containing the 2D values matrix and the expanded cycle lengths.
import numpy as np
from vector_ta import l1_ehlers_phasor_batch
result = l1_ehlers_phasor_batch(
data,
domestic_cycle_length_range=(15, 21, 3),
)
values = result["values"]
domestic_cycle_lengths = result["domestic_cycle_lengths"]
rows = result["rows"]
cols = result["cols"] JavaScript/WASM Bindings
Basic Usage ▼
WASM exports l1_ehlers_phasor_js(data, domestic_cycle_length) and returns a Float64Array.
import { l1_ehlers_phasor_js } from 'vectorta-wasm';
const data = new Float64Array([100.0, 101.0, 102.5, 101.8, 103.1, 104.4]);
const values = l1_ehlers_phasor_js(data, 15);
console.log(values); Memory-Efficient Operations ▼
Use l1_ehlers_phasor_alloc, l1_ehlers_phasor_into, and l1_ehlers_phasor_free to compute into caller-managed memory.
import {
l1_ehlers_phasor_alloc,
l1_ehlers_phasor_free,
l1_ehlers_phasor_into,
memory,
} from 'vectorta-wasm';
const len = data.length;
const dataPtr = l1_ehlers_phasor_alloc(len);
const outPtr = l1_ehlers_phasor_alloc(len);
new Float64Array(memory.buffer, dataPtr, len).set(data);
l1_ehlers_phasor_into(dataPtr, outPtr, len, 15);
const values = new Float64Array(memory.buffer, outPtr, len).slice();
l1_ehlers_phasor_free(dataPtr, len);
l1_ehlers_phasor_free(outPtr, len); Batch Processing ▼
WASM batch uses l1_ehlers_phasor_batch_js with domestic_cycle_length_range and returns values, combos, rows, and cols.
import { l1_ehlers_phasor_batch_js } from 'vectorta-wasm';
const batch = l1_ehlers_phasor_batch_js(data, {
domestic_cycle_length_range: [15, 21, 3],
});
console.log(batch.values);
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
Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)
Related Indicators
Aso
Technical analysis indicator
High-Low Correlation
Technical analysis indicator
Deviation
Technical analysis indicator
Ehlers Error-Correcting EMA (ECEMA)
Moving average indicator
Ehlers Instantaneous Trendline
Moving average indicator
Ehlers Kaufman Adaptive Moving Average
Moving average indicator