Neighboring Trailing Stop

Parameters: buffer_size = 200 | k = 50 | percentile = 90 | smooth = 5

Overview

Neighboring Trailing Stop maintains a sorted rolling buffer of recent closes, looks outward from the current close, and estimates nearby bullish and bearish bands from percentile samples of that local neighborhood. Those candidate bands are then smoothed separately and used to decide when directional discovery occurs.

The indicator returns a trailing stop, smoothed bullish and bearish bands, a directional state, and two discovery flags showing whether the latest update discovered a new bullish or bearish condition. Once direction flips, the trailing stop resets to the opposing band or the current bar extreme, then ratchets in the active direction.

Defaults: `buffer_size = 200`, `k = 50`, `percentile = 90.0`, and `smooth = 5`.

Implementation Examples

Run the indicator on candle highs, lows, and closes or on aligned slices.

use vector_ta::indicators::neighboring_trailing_stop::{
    neighboring_trailing_stop,
    NeighboringTrailingStopInput,
    NeighboringTrailingStopParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

let output = neighboring_trailing_stop(&NeighboringTrailingStopInput::from_slices(
    &high,
    &low,
    &close,
    NeighboringTrailingStopParams {
        buffer_size: Some(200),
        k: Some(50),
        percentile: Some(90.0),
        smooth: Some(5),
    },
))?;

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

println!("trailing stop = {:?}", output.trailing_stop.last());
println!("direction = {:?}", candle_output.direction.last());
println!("discovery bull = {:?}", candle_output.discovery_bull.last());

API Reference

Input Methods
// From candles
NeighboringTrailingStopInput::from_candles(&Candles, NeighboringTrailingStopParams)
    -> NeighboringTrailingStopInput

// From aligned high/low/close slices
NeighboringTrailingStopInput::from_slices(&[f64], &[f64], &[f64], NeighboringTrailingStopParams)
    -> NeighboringTrailingStopInput

// From candles with default parameters
NeighboringTrailingStopInput::with_default_candles(&Candles)
    -> NeighboringTrailingStopInput
Parameters Structure
pub struct NeighboringTrailingStopParams {
    pub buffer_size: Option<usize>, // default 200
    pub k: Option<usize>,           // default 50
    pub percentile: Option<f64>,    // default 90.0
    pub smooth: Option<usize>,      // default 5
}
Output Structure
pub struct NeighboringTrailingStopOutput {
    pub trailing_stop: Vec<f64>,
    pub bullish_band: Vec<f64>,
    pub bearish_band: Vec<f64>,
    pub direction: Vec<f64>,
    pub discovery_bull: Vec<f64>,
    pub discovery_bear: Vec<f64>,
}
Validation, Warmup & NaNs
  • The high, low, and close inputs must be non-empty and have matching lengths.
  • buffer_size must be at least 100.
  • k must be at least 5.
  • percentile must be finite and stay within 1.0..=99.0.
  • smooth must be greater than 0.
  • All-nonfinite input is rejected up front, and any non-finite bar resets the stream state.
  • The stream exposes get_warmup_period() -> 0, but the smoothed bands still return NaN until the SMA buffers fill.
  • Batch mode rejects unsupported kernels through InvalidKernelForBatch.
Builder, Streaming & Batch APIs
// Builder
NeighboringTrailingStopBuilder::new()
    .buffer_size(usize)
    .k(usize)
    .percentile(f64)
    .smooth(usize)
    .kernel(Kernel)
    .apply(&Candles)

NeighboringTrailingStopBuilder::new()
    .apply_slices(&[f64], &[f64], &[f64])

NeighboringTrailingStopBuilder::new()
    .into_stream()

// Stream
NeighboringTrailingStopStream::try_new(NeighboringTrailingStopParams)
NeighboringTrailingStopStream::update(f64, f64, f64)
    -> Option<NeighboringTrailingStopPoint>
NeighboringTrailingStopStream::reset()
NeighboringTrailingStopStream::get_warmup_period() -> usize

// Batch
NeighboringTrailingStopBatchBuilder::new()
    .buffer_size_range(usize, usize, usize)
    .k_range(usize, usize, usize)
    .percentile_range(f64, f64, f64)
    .smooth_range(usize, usize, usize)
    .kernel(Kernel)
    .apply_slices(&[f64], &[f64], &[f64])

NeighboringTrailingStopBatchBuilder::new()
    .apply(&Candles)
Error Handling
pub enum NeighboringTrailingStopError {
    EmptyInputData,
    AllValuesNaN,
    MismatchedInputLengths { high_len: usize, low_len: usize, close_len: usize },
    InvalidBufferSize { buffer_size: usize, min: usize },
    InvalidK { k: usize, min: usize },
    InvalidPercentile { percentile: f64 },
    InvalidSmooth { smooth: usize },
    OutputLengthMismatch { expected: usize },
    InvalidRange { start: String, end: String, step: String },
    InvalidKernelForBatch(Kernel),
}

Python Bindings

Python exposes a scalar function, a streaming class, and a batch sweep. The scalar function returns six NumPy arrays, the stream returns a six-value tuple or `None`, and batch returns six row-major matrices plus the tested parameter axes and output dimensions.

from vector_ta import (
    neighboring_trailing_stop,
    neighboring_trailing_stop_batch,
    NeighboringTrailingStopStream,
)

trailing_stop, bullish_band, bearish_band, direction, discovery_bull, discovery_bear = (
    neighboring_trailing_stop(
        highs,
        lows,
        closes,
        buffer_size=200,
        k=50,
        percentile=90.0,
        smooth=5,
        kernel="auto",
    )
)

stream = NeighboringTrailingStopStream(
    buffer_size=200,
    k=50,
    percentile=90.0,
    smooth=5,
)
print(stream.update(highs[-1], lows[-1], closes[-1]))

batch = neighboring_trailing_stop_batch(
    highs,
    lows,
    closes,
    buffer_size_range=(160, 240, 40),
    k_range=(30, 50, 10),
    percentile_range=(80.0, 95.0, 5.0),
    smooth_range=(3, 7, 2),
    kernel="auto",
)

print(batch["trailing_stop"].shape)
print(batch["buffer_sizes"])
print(batch["percentiles"])

JavaScript/WASM Bindings

The WASM surface exposes scalar, batch, allocation, free, and into-buffer entry points. Scalar output returns six arrays directly. Batch output returns those six flattened row-major outputs plus `combos`, the tested axes, and the row and column counts.

import init, {
  neighboring_trailing_stop_js,
  neighboring_trailing_stop_batch_js,
} from "@vectoralpha/vector_ta";

await init();

const single = neighboring_trailing_stop_js(
  highs,
  lows,
  closes,
  200,
  50,
  90.0,
  5,
);

console.log(single.trailing_stop);
console.log(single.bullish_band);
console.log(single.direction);

const batch = neighboring_trailing_stop_batch_js(highs, lows, closes, {
  buffer_size_range: [160, 240, 40],
  k_range: [30, 50, 10],
  percentile_range: [80.0, 95.0, 5.0],
  smooth_range: [3, 7, 2],
});

console.log(batch.trailing_stop);
console.log(batch.buffer_sizes);
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