L1 Ehlers Phasor

Parameters: 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() exposes domestic_cycle_length(), kernel(), apply(), apply_slice(), and into_stream().
  • L1EhlersPhasorStream::update(value) returns f64, not Option<f64>. Warmup and invalid windows produce NaN.
  • L1EhlersPhasorBatchRange contains only domestic_cycle_length: (start, end, step).
  • L1EhlersPhasorBatchBuilder::new() exposes domestic_cycle_length_range(), kernel(), apply(), and apply_slice().
  • Batch output is L1EhlersPhasorBatchOutput with values, combos, rows, and cols; values is flattened row-major.
Validation, Warmup & NaNs
  • Input must be non-empty and contain at least one finite value; otherwise the indicator returns EmptyInputData or AllValuesNaN.
  • domestic_cycle_length must be greater than zero.
  • Single-run validation requires at least domestic_cycle_length values from the first finite sample to the end of the slice; otherwise the indicator returns NotEnoughValidData.
  • Single-output warmup fills indices before first_valid + domestic_cycle_length - 1 with NaN.
  • Inside the core stream, any non-finite value inside the active ring buffer makes the output NaN until 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

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