MinMax (Local Extrema)

Parameters: 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 > 0 and order ≤ len; otherwise MinmaxError::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, returns MinmaxError::NotEnoughValidData.
  • Indices before the first finite pair are NaN for all outputs.
  • is_min/is_max require all neighbors on both sides to be finite and use strict inequalities.
  • Near edges (i < order or i + order ≥ len), extrema are NaN.
  • last_min/last_max forward‑fill after the first detected extrema; remain NaN until 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

Comparison:
View:

Across sizes, Rust CPU runs about 4.51× slower than Tulip C in this benchmark.

Loading chart...

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.

Related Indicators