HalfTrend
amplitude = 2 | channel_deviation = 2 | atr_period = 100 Overview
HalfTrend generates crystal clear trend signals by constructing a step function that switches between tracking the highest recent lows during uptrends and the lowest recent highs during downtrends, creating an adaptive support and resistance line that only flips when price decisively breaks through volatility adjusted boundaries. The indicator monitors rolling price extremes within a specified amplitude window, then applies ATR based channels to filter out false breakouts that would trigger premature reversals in choppy conditions. When price closes beyond the channel deviation from the current trend line and confirms with a move past recent extremes, HalfTrend flips direction and generates an explicit buy or sell arrow at the exact reversal point. Traders appreciate HalfTrend for its binary clarity where the market is either definitively long or short with no ambiguous middle ground, eliminating the analysis paralysis that plagues multi signal systems. The visual simplicity of following a single colored line that stays on one side of price during trends makes it particularly effective for newer traders learning trend following concepts. Furthermore, the combination of price structure (highs and lows) with volatility filtering (ATR channels) creates robust signals that adapt to changing market conditions while maintaining consistent risk parameters across different volatility regimes.
Implementation Examples
Calculate HalfTrend from OHLC slices or Candles:
use vectorta::indicators::halftrend::{halftrend, HalfTrendInput, HalfTrendParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// Using with OHLC slices
let high = vec![1.0, 1.2, 1.1, 1.3, 1.4, 1.35];
let low = vec![0.9, 1.0, 1.0, 1.1, 1.2, 1.25];
let close = vec![1.0, 1.1, 1.05, 1.2, 1.3, 1.28];
let params = HalfTrendParams { amplitude: Some(2), channel_deviation: Some(2.0), atr_period: Some(100) };
let input = HalfTrendInput::from_slices(&high, &low, &close, params);
let output = halftrend(&input)?;
// Or with Candles and default parameters
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = HalfTrendInput::with_default_candles(&candles);
let output = halftrend(&input)?;
// Access results
println!("HT[0] = {:?}", output.halftrend[0]);
println!("Trend[0] = {:?}", output.trend[0]);
println!("ATR_HIGH[0] = {:?}", output.atr_high[0]);
println!("ATR_LOW[0] = {:?}", output.atr_low[0]);
// buy_signal/sell_signal are NaN except on flip bars
API Reference
Input Methods ▼
// From OHLC slices
HalfTrendInput::from_slices(&[f64], &[f64], &[f64], HalfTrendParams) -> HalfTrendInput
// From Candles
HalfTrendInput::from_candles(&Candles, HalfTrendParams) -> HalfTrendInput
// Candles with defaults (amplitude=2, channel_deviation=2.0, atr_period=100)
HalfTrendInput::with_default_candles(&Candles) -> HalfTrendInput Parameters Structure ▼
pub struct HalfTrendParams {
pub amplitude: Option<usize>, // Default: 2
pub channel_deviation: Option<f64>, // Default: 2.0 (must be > 0)
pub atr_period: Option<usize>, // Default: 100
} Output Structure ▼
pub struct HalfTrendOutput {
pub halftrend: Vec<f64>, // main step line
pub trend: Vec<f64>, // 0.0 = uptrend, 1.0 = downtrend
pub atr_high: Vec<f64>, // upper volatility band (HT + dev)
pub atr_low: Vec<f64>, // lower volatility band (HT - dev)
pub buy_signal: Vec<f64>, // price on flip-up bars; NaN otherwise
pub sell_signal: Vec<f64>, // price on flip-down bars; NaN otherwise
} Validation, Warmup & NaNs ▼
- Inputs must be non-empty and equal length; otherwise
HalfTrendError::InvalidPeriod. amplitude > 0,atr_period > 0, andchannel_deviation > 0(finite); elseInvalidPeriodorInvalidChannelDeviation.- First valid index is the earliest bar where any OHLC is finite; if none:
AllValuesNaN. - Warmup span =
max(amplitude, atr_period); if insufficient valid data after first finite bar:NotEnoughValidData. - All outputs are
NaNbefore the warm index; values compute from there forward.
Error Handling ▼
use vectorta::indicators::halftrend::{halftrend, HalfTrendError};
match halftrend(&input) {
Ok(out) => process(out),
Err(HalfTrendError::EmptyInputData) => eprintln!("Input data is empty"),
Err(HalfTrendError::AllValuesNaN) => eprintln!("All OHLC values are NaN"),
Err(HalfTrendError::InvalidPeriod { period, data_len }) =>
eprintln!("Invalid period {} for data length {}", period, data_len),
Err(HalfTrendError::NotEnoughValidData { needed, valid }) =>
eprintln!("Need {} valid points, only {} available", needed, valid),
Err(HalfTrendError::InvalidChannelDeviation { channel_deviation }) =>
eprintln!("channel_deviation must be > 0, got {}", channel_deviation),
Err(HalfTrendError::AtrError(e)) | Err(HalfTrendError::SmaError(e)) =>
eprintln!("Dependency error: {}", e),
} Python Bindings
Basic Usage ▼
Compute HalfTrend using NumPy arrays (defaults: amplitude=2, channel_deviation=2.0, atr_period=100):
import numpy as np
from vectorta import halftrend
high = np.array([1.0, 1.2, 1.1, 1.3], dtype=float)
low = np.array([0.9, 1.0, 1.0, 1.1], dtype=float)
close = np.array([1.0, 1.1, 1.05, 1.2], dtype=float)
# Positional args: (high, low, close, amplitude, channel_deviation, atr_period)
out = halftrend(high, low, close, 2, 2.0, 100)
# Output is a dict with series arrays
ht = out['halftrend']; tr = out['trend']
ah = out['atr_high']; al = out['atr_low']
bs = out['buy_signal']; ss = out['sell_signal']
print(ht[:5], tr[:5]) Streaming Real-time Updates ▼
Stream OHLC updates and react to flip markers:
from vectorta import HalfTrendStream
stream = HalfTrendStream(amplitude=2, channel_deviation=2.0, atr_period=100)
for (h, l, c) in ohlc_feed:
tup = stream.update(h, l, c)
if tup is not None:
halftrend, trend, atr_high, atr_low, buy_signal, sell_signal = tup
# buy/sell are None except on the flip bar
handle(halftrend, trend, atr_high, atr_low, buy_signal, sell_signal) Batch Parameter Optimization ▼
Sweep amplitude, channel_deviation, and atr_period:
import numpy as np
from vectorta import halftrend_batch
high = np.array([...], dtype=float)
low = np.array([...], dtype=float)
close = np.array([...], dtype=float)
res = halftrend_batch(
high, low, close,
amplitude_start=2, amplitude_end=4, amplitude_step=1,
channel_deviation_start=1.5, channel_deviation_end=2.5, channel_deviation_step=0.5,
atr_period_start=50, atr_period_end=150, atr_period_step=50,
kernel='auto'
)
# Results: dict with 'values' shaped [6*rows, cols] and parameter arrays
vals = res['values']
series = res['series'] # ['halftrend','trend','atr_high','atr_low','buy','sell']
amps = res['amplitudes']; chs = res['channel_deviations']; aps = res['atr_periods'] CUDA Acceleration ▼
CUDA support for HalfTrend is coming soon.
# Coming soon: CUDA-accelerated HalfTrend calculations JavaScript/WASM Bindings
Basic Usage ▼
Compute HalfTrend in JavaScript/TypeScript:
import { halftrend, halftrend_wasm } from 'vectorta-wasm';
const high = new Float64Array([1.0, 1.2, 1.1, 1.3]);
const low = new Float64Array([0.9, 1.0, 1.0, 1.1]);
const close = new Float64Array([1.0, 1.1, 1.05, 1.2]);
// Explicit parameters
const out = halftrend(high, low, close, 2, 2.0, 100);
// Optional defaults via *_wasm variant (omit parameters)
const out2 = halftrend_wasm(high, low, close, null, null, null);
console.log(out); // structure contains series arrays (implementation-specific) Memory-Efficient Operations ▼
Use zero-copy operations with manual memory management:
import { halftrend_alloc, halftrend_free, halftrend_into, memory } from 'vectorta-wasm';
const len = high.length;
// Allocate WASM memory for inputs and outputs
const hPtr = halftrend_alloc(len);
const lPtr = halftrend_alloc(len);
const cPtr = halftrend_alloc(len);
// Output layout is implementation-defined; allocate one or more buffers as required
const outPtr = halftrend_alloc(len); // example allocation
// Copy inputs into WASM memory
new Float64Array(memory.buffer, hPtr, len).set(high);
new Float64Array(memory.buffer, lPtr, len).set(low);
new Float64Array(memory.buffer, cPtr, len).set(close);
// Compute into pre-allocated memory
halftrend_into(hPtr, lPtr, cPtr, outPtr, len, 2, 2.0, 100);
// Read back results as needed
const result = new Float64Array(memory.buffer, outPtr, len).slice();
// Free memory
halftrend_free(hPtr, len);
halftrend_free(lPtr, len);
halftrend_free(cPtr, len);
halftrend_free(outPtr, len); Batch Processing ▼
Test multiple combinations efficiently:
import { halftrend_batch } from 'vectorta-wasm';
const cfg = {
amplitude_range: [2, 4, 1],
channel_deviation_range: [1.5, 2.5, 0.5],
atr_period_range: [50, 150, 50]
};
const res = halftrend_batch(high, low, close, cfg);
console.log(res); // structure with 6*rows × cols flattened values and metadata Performance Analysis
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05
CUDA note
In our benchmark workload, the Rust CPU implementation is faster than CUDA for this indicator. Prefer the Rust/CPU path unless your workload differs.