Chande Exits (Chandelier Exits)
period = 22 | mult = 3 (0.5–5) | direction = long Overview
Chandelier Exits, developed by Chuck LeBeau, provide volatility based trailing stops that adapt to market conditions by combining Average True Range (ATR) with rolling price extremes. For long positions, the indicator calculates the highest high over the specified period and subtracts a multiple of ATR to create a dynamic stop level below the market. Short positions use the lowest low plus a multiple of ATR to position the stop above current prices. This approach ensures stops remain at a distance proportional to current volatility.
The indicator excels at protecting profits in trending markets while avoiding premature exits during normal price fluctuations. As volatility increases, the stop moves further from price to accommodate larger swings. When volatility contracts, the stop tightens to preserve more gains. Typical ATR multipliers range from 2.5 to 3.5, with higher values providing more breathing room but potentially giving back larger portions of unrealized profits.
Traders primarily use Chandelier Exits for position management rather than entry signals. The indicator works particularly well with trend following systems where it allows winners to run while cutting losses systematically. Unlike fixed percentage stops, Chandelier Exits adjust to market character, making them effective across different instruments and market conditions. The trailing nature ensures the stop only moves in the favorable direction, locking in profits as trends extend.
Implementation Examples
Get adaptive trailing stops in a few lines:
use vectorta::indicators::chande::{chande, ChandeInput, ChandeParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// From high/low/close slices
let high = vec![/* ... */];
let low = vec![/* ... */];
let close = vec![/* ... */];
let params = ChandeParams { period: Some(22), mult: Some(3.0), direction: Some("long".into()) };
let input = ChandeInput::from_slices(&high, &low, &close, params);
let result = chande(&input)?; // result.values: Vec<f64>
// From Candles with defaults (period=22, mult=3.0, direction="long")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = ChandeInput::with_default_candles(&candles);
let result = chande(&input)?; API Reference
Input Methods ▼
// From high/low/close slices
ChandeInput::from_slices(&[f64], &[f64], &[f64], ChandeParams) -> ChandeInput
// From candles
ChandeInput::from_candles(&Candles, ChandeParams) -> ChandeInput
// From candles with default params (period=22, mult=3.0, direction="long")
ChandeInput::with_default_candles(&Candles) -> ChandeInput Parameters Structure ▼
pub struct ChandeParams {
pub period: Option<usize>, // Default: 22
pub mult: Option<f64>, // Default: 3.0
pub direction: Option<String>, // Default: "long" ("long" or "short")
} Output Structure ▼
pub struct ChandeOutput {
pub values: Vec<f64>, // trailing stop levels (per direction)
} Validation, Warmup & NaNs ▼
- Requires non-empty
high/low/closeand equal lengths; elseChandeError::EmptyInputDataorDataLengthMismatch. - First finite triple index is used; if none,
ChandeError::AllValuesNaN. period > 0andperiod ≤ len; otherwiseChandeError::InvalidPeriod.- Need at least
periodvalid points after first finite; otherwiseChandeError::NotEnoughValidData. directionmust be"long"or"short"; otherwiseChandeError::InvalidDirection.- Warmup: indices
[0 .. first+period-2]areNaNviaalloc_with_nan_prefix. - Streaming:
ChandeStream::updatereturnsNoneuntil warmup completes; thenSome(value)each step.
Error Handling ▼
use vectorta::indicators::chande::{chande, ChandeError};
match chande(&input) {
Ok(out) => process(out.values),
Err(ChandeError::EmptyInputData) => eprintln!("empty input"),
Err(ChandeError::AllValuesNaN) => eprintln!("no finite values"),
Err(ChandeError::InvalidPeriod { period, data_len }) => eprintln!("invalid period {} for len {}", period, data_len),
Err(ChandeError::NotEnoughValidData { needed, valid }) => eprintln!("need {} valid points, got {}", needed, valid),
Err(ChandeError::DataLengthMismatch { h, l, c }) => eprintln!("len mismatch: h={}, l={}, c={}", h, l, c),
Err(ChandeError::InvalidDirection { direction }) => eprintln!("invalid direction: {}", direction),
} Python Bindings
Basic Usage ▼
import numpy as np
from vectorta import chande, ChandeStream
# High/Low/Close arrays
high = np.array([...], dtype=np.float64)
low = np.array([...], dtype=np.float64)
close = np.array([...], dtype=np.float64)
# Single run
values = chande(high, low, close, period=22, mult=3.0, direction="long")
# Streaming
stream = ChandeStream(22, 3.0, "long")
out = []
for h, l, c in zip(high, low, close):
v = stream.update(h, l, c)
out.append(np.nan if v is None else v) Batch Processing ▼
import numpy as np
from vectorta import chande_batch
high = np.array([...], dtype=np.float64)
low = np.array([...], dtype=np.float64)
close = np.array([...], dtype=np.float64)
# Ranges as (start, end, step)
res = chande_batch(high, low, close, (14, 30, 2), (2.0, 4.0, 0.5), "long")
# Access fields
values = res['values'] # shape: (num_combos, len)
periods = res['periods'] # list of periods for each row
mults = res['mults'] # list of mults for each row
directions = res['directions'] CUDA Acceleration ▼
CUDA support for this indicator is coming soon.
JavaScript/WASM Bindings
Basic Usage ▼
Calculate Chande Exits in JavaScript/TypeScript:
import { chande_js } from 'vectorta-wasm';
const high = new Float64Array([/* ... */]);
const low = new Float64Array([/* ... */]);
const close = new Float64Array([/* ... */]);
// period, mult, direction ("long" or "short")
const values = chande_js(high, low, close, 22, 3.0, "long");
console.log('Chande values:', values); Memory-Efficient Operations ▼
Use zero-copy operations for large datasets:
import { chande_alloc, chande_free, chande_into, memory } from 'vectorta-wasm';
const len = close.length;
const hPtr = chande_alloc(len);
const lPtr = chande_alloc(len);
const cPtr = chande_alloc(len);
const outPtr = chande_alloc(len);
new Float64Array(memory.buffer, hPtr, len).set(high);
new Float64Array(memory.buffer, lPtr, len).set(low);
new Float64Array(memory.buffer, cPtr, len).set(close);
chande_into(hPtr, lPtr, cPtr, outPtr, len, 22, 3.0, "long");
const stops = new Float64Array(memory.buffer, outPtr, len).slice();
chande_free(hPtr, len);
chande_free(lPtr, len);
chande_free(cPtr, len);
chande_free(outPtr, len); Batch Processing ▼
Test multiple parameter combinations:
import { chande_batch_js } from 'vectorta-wasm';
const out = chande_batch_js(
high, low, close,
14, 30, 2, // period range
2.0, 4.0, 0.5, // mult range
"long"
);
// Returned object contains: { values: Float64Array, periods: Float64Array, mults: Float64Array, directions: string[], rows, cols }
console.log(out.rows, out.cols); Performance Analysis
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05