HEMA Trend Levels

Parameters: fast_length = 20 | slow_length = 40

Overview

HEMA Trend Levels uses a fast and a slow Hull-style exponential moving average to classify trend direction and state changes. The fast line responds more quickly to price, the slow line anchors the broader trend, and the distance between them is used to identify bullish crossovers, bearish crossunders, and whether the current bar is operating in a bullish or bearish regime.

On top of the moving-average core, the indicator builds ATR-scaled structural boxes that mark trend continuation and retest areas. The output includes box offsets, bullish and bearish box boundaries, crossover flags, retest flags, and the test levels themselves, so the result can drive both directional filters and level-based trade management from the same calculation.

Defaults: HEMA Trend Levels uses `fast_length = 20` and `slow_length = 40`. Internal ATR settings are fixed at `period = 14` and `scale = 0.5`.

Implementation Examples

Compute the full trend-level pack from candles or direct OHLC slices.

use vector_ta::indicators::hema_trend_levels::{
    hema_trend_levels,
    HemaTrendLevelsInput,
    HemaTrendLevelsParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

let output = hema_trend_levels(&HemaTrendLevelsInput::from_slices(
    &open,
    &high,
    &low,
    &close,
    HemaTrendLevelsParams {
        fast_length: Some(20),
        slow_length: Some(40),
    },
))?;

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

println!("{:?}", output.trend_direction.last());
println!("{:?}", candle_output.bull_box_top.last());

API Reference

Input Methods
// From candles
HemaTrendLevelsInput::from_candles(&Candles, HemaTrendLevelsParams)
    -> HemaTrendLevelsInput

// From OHLC slices
HemaTrendLevelsInput::from_slices(&[f64], &[f64], &[f64], &[f64], HemaTrendLevelsParams)
    -> HemaTrendLevelsInput

// From candles with default parameters
HemaTrendLevelsInput::with_default_candles(&Candles)
    -> HemaTrendLevelsInput
Parameters Structure
pub struct HemaTrendLevelsParams {
    pub fast_length: Option<usize>,
    pub slow_length: Option<usize>,
}
Output Structure
pub struct HemaTrendLevelsOutput {
    pub fast_hema: Vec<f64>,
    pub slow_hema: Vec<f64>,
    pub trend_direction: Vec<f64>,
    pub bar_state: Vec<f64>,
    pub bullish_crossover: Vec<f64>,
    pub bearish_crossunder: Vec<f64>,
    pub box_offset: Vec<f64>,
    pub bull_box_top: Vec<f64>,
    pub bull_box_bottom: Vec<f64>,
    pub bear_box_top: Vec<f64>,
    pub bear_box_bottom: Vec<f64>,
    pub bullish_test: Vec<f64>,
    pub bearish_test: Vec<f64>,
    pub bullish_test_level: Vec<f64>,
    pub bearish_test_level: Vec<f64>,
}
Validation, Warmup & NaNs
  • All four OHLC inputs must be non-empty and share the same length.
  • fast_length and slow_length must both be greater than 0.
  • Scalar and batch modes reject fully invalid input through AllValuesNaN.
  • Streaming resets whenever an incoming OHLC bar contains a non-finite value.
  • get_warmup_period() currently reports 0, so the stream is available immediately after valid bars arrive.
  • Batch mode validates range axes and rejects unsupported batch kernels through InvalidKernelForBatch.
Builder, Streaming & Batch APIs
// Builder
HemaTrendLevelsBuilder::new()
    .fast_length(usize)
    .slow_length(usize)
    .kernel(Kernel)
    .apply(&Candles)

HemaTrendLevelsBuilder::new()
    .apply_slices(&[f64], &[f64], &[f64], &[f64])
    .into_stream()

// Stream
HemaTrendLevelsStream::try_new(HemaTrendLevelsParams)
HemaTrendLevelsStream::update(f64, f64, f64, f64) -> Option<HemaTrendLevelsPoint>
HemaTrendLevelsStream::reset()
HemaTrendLevelsStream::get_warmup_period() -> usize

// Batch
HemaTrendLevelsBatchBuilder::new()
    .fast_length_range(usize, usize, usize)
    .slow_length_range(usize, usize, usize)
    .kernel(Kernel)
    .apply_slices(&[f64], &[f64], &[f64], &[f64])
Error Handling
pub enum HemaTrendLevelsError {
    EmptyInputData,
    AllValuesNaN,
    InconsistentSliceLengths { open_len: usize, high_len: usize, low_len: usize, close_len: usize },
    InvalidFastLength { fast_length: usize },
    InvalidSlowLength { slow_length: usize },
    OutputLengthMismatch { expected: usize },
    InvalidRange { start: String, end: String, step: String },
    InvalidKernelForBatch(Kernel),
}

Python Bindings

Python exposes a scalar OHLC function, a streaming class, and a batch sweep. The scalar binding returns all fifteen output arrays in a dictionary. Streaming returns one dictionary per valid bar. Batch returns reshaped matrices for every field plus the tested fast and slow lengths.

import numpy as np
from vector_ta import (
    hema_trend_levels,
    hema_trend_levels_batch,
    HemaTrendLevelsStream,
)

open_ = np.asarray(open_values, dtype=np.float64)
high = np.asarray(high_values, dtype=np.float64)
low = np.asarray(low_values, dtype=np.float64)
close = np.asarray(close_values, dtype=np.float64)

result = hema_trend_levels(
    open_,
    high,
    low,
    close,
    fast_length=20,
    slow_length=40,
    kernel="auto",
)

stream = HemaTrendLevelsStream(fast_length=20, slow_length=40)
print(stream.update(float(open_[-1]), float(high[-1]), float(low[-1]), float(close[-1])))
print(stream.warmup_period)

batch = hema_trend_levels_batch(
    open_,
    high,
    low,
    close,
    fast_length_range=(16, 24, 4),
    slow_length_range=(32, 48, 8),
    kernel="auto",
)

print(batch["trend_direction"].shape)
print(batch["fast_lengths"])
print(batch["slow_lengths"])

JavaScript/WASM Bindings

The WASM layer exposes scalar and batch helpers plus lower-level buffer APIs. The scalar binding returns every HEMA line, state flag, and box level array. The batch binding returns flattened arrays for the same fields, together with the tested fast and slow lengths and the matrix dimensions.

import init, {
  hema_trend_levels_js,
  hema_trend_levels_batch_js,
} from "@vectoralpha/vector_ta";

await init();

const single = hema_trend_levels_js(open, high, low, close, 20, 40);

console.log(single.fast_hema);
console.log(single.trend_direction);
console.log(single.bull_box_top);

const batch = hema_trend_levels_batch_js(open, high, low, close, {
  fast_length_range: [16, 24, 4],
  slow_length_range: [32, 48, 8],
});

console.log(batch.fast_lengths);
console.log(batch.slow_lengths);
console.log(batch.rows, batch.cols);

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