MinMax (Local Extrema)
order = 3 Overview
The MinMax indicator identifies local peaks and valleys in price action by examining a symmetric neighborhood of bars around each point, marking strict extrema where a low is lower than all surrounding lows or a high exceeds all neighboring highs. This detection uses an order parameter that defines how many bars to check on each side, with order 3 requiring a low to be the lowest point among 7 bars total (3 before, itself, and 3 after) to qualify as a local minimum. Beyond marking these turning points, MinMax maintains forward filled levels showing the last detected minimum and maximum, creating horizontal reference lines that update only when new extrema are found. Traders apply these extrema levels for identifying support and resistance zones, timing entries at local bottoms, or setting stop losses beyond recent peaks. The indicator excels at filtering out minor fluctuations to reveal significant turning points, though its requirement for future confirmation means signals appear with a delay equal to the order parameter.
Implementation Examples
Compute MinMax from slices or candles:
use vectorta::indicators::minmax::{minmax, MinmaxInput, MinmaxParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// From high/low slices
let high = vec![50.0, 55.0, 60.0, 55.0, 50.0, 45.0, 50.0, 55.0];
let low = vec![40.0, 38.0, 35.0, 38.0, 40.0, 42.0, 41.0, 39.0];
let params = MinmaxParams { order: Some(3) }; // default is 3
let input = MinmaxInput::from_slices(&high, &low, params);
let out = minmax(&input)?;
// From Candles (uses sources "high" / "low")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = MinmaxInput::with_default_candles(&candles);
let out = minmax(&input)?;
// Access results
for i in 0..out.is_min.len() {
// is_min/is_max are NaN except at detected extrema
let min_here = out.is_min[i];
let max_here = out.is_max[i];
let last_min = out.last_min[i];
let last_max = out.last_max[i];
// ... use values
} API Reference
Input Methods ▼
// From high/low slices
MinmaxInput::from_slices(&[f64], &[f64], MinmaxParams) -> MinmaxInput
// From candles with custom sources
MinmaxInput::from_candles(&Candles, &str, &str, MinmaxParams) -> MinmaxInput
// From candles with defaults (sources: "high" / "low"; order=3)
MinmaxInput::with_default_candles(&Candles) -> MinmaxInput Parameters Structure ▼
pub struct MinmaxParams {
pub order: Option<usize>, // Default: 3
} Output Structure ▼
pub struct MinmaxOutput {
pub is_min: Vec<f64>, // low at strict local minima; NaN otherwise
pub is_max: Vec<f64>, // high at strict local maxima; NaN otherwise
pub last_min: Vec<f64>,// forward-filled last detected minimum
pub last_max: Vec<f64>,// forward-filled last detected maximum
} Validation, Warmup & NaNs ▼
order > 0andorder ≤ len; otherwiseMinmaxError::InvalidOrder.- High/low slices must be the same length; mismatch yields
MinmaxError::InvalidOrder. - First finite pair index is detected; if none,
MinmaxError::AllValuesNaN. - If
len − first_valid_idx < order, returnsMinmaxError::NotEnoughValidData. - Indices before the first finite pair are
NaNfor all outputs. is_min/is_maxrequire all neighbors on both sides to be finite and use strict inequalities.- Near edges (
i < orderori + order ≥ len), extrema areNaN. last_min/last_maxforward‑fill after the first detected extrema; remainNaNuntil then.
Error Handling ▼
use vectorta::indicators::minmax::{minmax, MinmaxInput, MinmaxParams, MinmaxError};
match minmax(&MinmaxInput::from_slices(&high, &low, MinmaxParams { order: Some(3) })) {
Ok(out) => {
// use out.is_min, out.is_max, out.last_min, out.last_max
}
Err(MinmaxError::EmptyData) => eprintln!("Empty data"),
Err(MinmaxError::InvalidOrder { order, data_len }) => {
eprintln!("Invalid order {} for data_len {}", order, data_len)
}
Err(MinmaxError::NotEnoughValidData { needed, valid }) => {
eprintln!("Need {} valid points, have {}", needed, valid)
}
Err(MinmaxError::AllValuesNaN) => eprintln!("All values are NaN"),
} Python Bindings
Basic Usage ▼
NumPy arrays for high/low; returns four arrays:
import numpy as np
from vectorta import minmax
high = np.asarray([...], dtype=np.float64)
low = np.asarray([...], dtype=np.float64)
is_min, is_max, last_min, last_max = minmax(high, low, order=3, kernel="auto")
print(is_min.shape, is_max.shape, last_min.shape, last_max.shape) Streaming Updates ▼
from vectorta import MinmaxStream
stream = MinmaxStream(order=3)
for h, l in zip(high_stream, low_stream):
min_opt, max_opt, last_min, last_max = stream.update(h, l)
# handle new extrema if present Batch Sweeps ▼
from vectorta import minmax_batch
result = minmax_batch(high, low, order_range=(2, 10, 2), kernel="auto")
is_min = result["is_min"] # shape: (rows, len)
is_max = result["is_max"] # shape: (rows, len)
last_min= result["last_min"] # shape: (rows, len)
last_max= result["last_max"] # shape: (rows, len)
orders = result["orders"] # list of tested order values CUDA Acceleration ▼
CUDA support for MinMax is currently under development.
# Coming soon: CUDA-accelerated MinMax batch APIs JavaScript / WASM
Quick Usage ▼
Call MinMax and reshape the four outputs:
import { minmax_js } from 'vectorta-wasm';
const high = new Float64Array([/* highs */]);
const low = new Float64Array([/* lows */]);
const res = minmax_js(high, low, 3); // { values, rows: 4, cols: len }
const cols = res.cols;
const isMin = res.values.slice(0 * cols, 1 * cols);
const isMax = res.values.slice(1 * cols, 2 * cols);
const lastMin = res.values.slice(2 * cols, 3 * cols);
const lastMax = res.values.slice(3 * cols, 4 * cols); Memory-Efficient Operations ▼
Allocate once and compute directly into WASM memory:
import { minmax_alloc, minmax_free, minmax_into, memory } from 'vectorta-wasm';
const len = high.length;
const highPtr = minmax_alloc(len);
const lowPtr = minmax_alloc(len);
const isMinPtr = minmax_alloc(len);
const isMaxPtr = minmax_alloc(len);
const lastMinPtr = minmax_alloc(len);
const lastMaxPtr = minmax_alloc(len);
new Float64Array(memory.buffer, highPtr, len).set(high);
new Float64Array(memory.buffer, lowPtr, len).set(low);
minmax_into(highPtr, lowPtr, isMinPtr, isMaxPtr, lastMinPtr, lastMaxPtr, len, 3);
const isMin = new Float64Array(memory.buffer, isMinPtr, len).slice();
const isMax = new Float64Array(memory.buffer, isMaxPtr, len).slice();
const lastMin = new Float64Array(memory.buffer, lastMinPtr, len).slice();
const lastMax = new Float64Array(memory.buffer, lastMaxPtr, len).slice();
minmax_free(highPtr, len);
minmax_free(lowPtr, len);
minmax_free(isMinPtr, len);
minmax_free(isMaxPtr, len);
minmax_free(lastMinPtr, len);
minmax_free(lastMaxPtr, len); Batch Processing ▼
Sweep order and parse series-major output:
import { minmax_batch as minmax_batch_unified } from 'vectorta-wasm';
const out = minmax_batch_unified(high, low, { order_range: [2, 10, 2] });
// out: { values: Float64Array, combos: [{ order }, ...], rows: 4 * combos, cols: len }
const series = 0; // 0=is_min, 1=is_max, 2=last_min, 3=last_max
const combos = out.rows / 4;
const block = out.cols;
// Get series s for combo i
function sliceSeries(s: number, i: number) {
const offset = s * combos * block + i * block;
return out.values.slice(offset, offset + block);
}
const firstComboIsMin = sliceSeries(0, 0); Performance Analysis
Across sizes, Rust CPU runs about 4.51× slower than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05
Benchmark note
VectorTA’s MinMax implementation follows a different definition than Tulip C’s “minmax”. Treat Tulip C vs VectorTA timings here as comparing two different indicators rather than a like-for-like implementation.