Adjustable MA Alternating Extremities

Parameters: length = 50 | mult = 2 | alpha = 1 | beta = 0.5

Overview

Adjustable MA Alternating Extremities smooths high, low, and close with a custom weighted moving average, then builds symmetric bands around the smoothed close from the rolling average absolute distance between price and that moving average. Rather than treating the band pair as a passive envelope, the indicator tracks whether price is currently operating off the upper side or the lower side and promotes that active boundary into a dedicated extremity output.

The result is useful when you want both a smoothed structure and a simple regime marker. The ma series is the centerline, upper and lower define the adaptive envelope, state shows which side is active, changed flags boundary flips, and extremity publishes the currently selected live edge. The smoothed open, high, low, and close outputs make it possible to plot the transformed structure directly instead of reconstructing it from the internal weights.

Defaults: Adjustable MA Alternating Extremities uses `length = 50`, `mult = 2.0`, `alpha = 1.0`, and `beta = 0.5`.

Implementation Examples

Compute the indicator from high, low, and close slices or from a candle container.

use vector_ta::indicators::adjustable_ma_alternating_extremities::{
    adjustable_ma_alternating_extremities,
    AdjustableMaAlternatingExtremitiesInput,
    AdjustableMaAlternatingExtremitiesParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

let high = vec![101.0, 102.4, 103.1, 102.7, 104.9, 105.5];
let low = vec![99.8, 100.7, 101.6, 101.2, 103.0, 104.0];
let close = vec![100.6, 101.9, 102.7, 101.8, 104.1, 104.8];

let params = AdjustableMaAlternatingExtremitiesParams {
    length: Some(50),
    mult: Some(2.0),
    alpha: Some(1.0),
    beta: Some(0.5),
};

let output = adjustable_ma_alternating_extremities(
    &AdjustableMaAlternatingExtremitiesInput::from_slices(&high, &low, &close, params)
)?;

let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let candle_output = adjustable_ma_alternating_extremities(
    &AdjustableMaAlternatingExtremitiesInput::with_default_candles(&candles)
)?;

println!("Latest MA: {:?}", output.ma.last());
println!("Latest extremity: {:?}", output.extremity.last());
println!("Latest state: {:?}", output.state.last());
println!("Smoothed close length: {}", candle_output.smoothed_close.len());

API Reference

Input Methods
// From candles
AdjustableMaAlternatingExtremitiesInput::from_candles(&Candles, AdjustableMaAlternatingExtremitiesParams)
    -> AdjustableMaAlternatingExtremitiesInput

// From high/low/close slices
AdjustableMaAlternatingExtremitiesInput::from_slices(
    &[f64],
    &[f64],
    &[f64],
    AdjustableMaAlternatingExtremitiesParams,
) -> AdjustableMaAlternatingExtremitiesInput

// From candles with default parameters
AdjustableMaAlternatingExtremitiesInput::with_default_candles(&Candles)
    -> AdjustableMaAlternatingExtremitiesInput
Parameters Structure
pub struct AdjustableMaAlternatingExtremitiesParams {
    pub length: Option<usize>, // default 50
    pub mult: Option<f64>,     // default 2.0
    pub alpha: Option<f64>,    // default 1.0
    pub beta: Option<f64>,     // default 0.5
}
Output Structure
pub struct AdjustableMaAlternatingExtremitiesOutput {
    pub ma: Vec<f64>,
    pub upper: Vec<f64>,
    pub lower: Vec<f64>,
    pub extremity: Vec<f64>,
    pub state: Vec<f64>,
    pub changed: Vec<f64>,
    pub smoothed_open: Vec<f64>,
    pub smoothed_high: Vec<f64>,
    pub smoothed_low: Vec<f64>,
    pub smoothed_close: Vec<f64>,
}
Validation, Warmup & NaNs
  • high.len() == low.len() == close.len(); otherwise the function returns DataLengthMismatch.
  • The first finite bar across all three inputs is used as the warmup anchor. If no finite bar exists, the function returns AllValuesNaN.
  • length must be at least 2 and no larger than the data length.
  • mult must be finite and at least 1.0. alpha and beta must be finite and non-negative.
  • The custom weight builder can fail with DegenerateKernel if the chosen alpha/beta pair collapses the weighting curve.
  • At least (length * 2) - 1 valid bars are required after the first finite index. The moving-average outputs warm up at first + length - 1, the bands and regime outputs warm up at first + (length * 2) - 2, and smoothed_open starts two bars after the moving average because it depends on prior smoothed closes.
  • Batch mode accepts only batch-capable kernels; otherwise InvalidKernelForBatch is returned.
Builder, Streaming & Batch APIs
// Builder
AdjustableMaAlternatingExtremitiesBuilder::new()
    .length(usize)
    .mult(f64)
    .alpha(f64)
    .beta(f64)
    .kernel(Kernel)
    .apply_slices(&[f64], &[f64], &[f64])

AdjustableMaAlternatingExtremitiesBuilder::new()
    .apply(&Candles)

AdjustableMaAlternatingExtremitiesBuilder::new()
    .into_stream()

// Stream
AdjustableMaAlternatingExtremitiesStream::try_new(AdjustableMaAlternatingExtremitiesParams)
AdjustableMaAlternatingExtremitiesStream::update(high, low, close)
    -> Option<AdjustableMaAlternatingExtremitiesStreamOutput>

// Batch
AdjustableMaAlternatingExtremitiesBatchBuilder::new()
    .range(AdjustableMaAlternatingExtremitiesBatchRange)
    .kernel(Kernel)
    .apply_slices(&[f64], &[f64], &[f64])

AdjustableMaAlternatingExtremitiesBatchBuilder::new()
    .apply(&Candles)
Error Handling
pub enum AdjustableMaAlternatingExtremitiesError {
    EmptyInputData,
    DataLengthMismatch { high: usize, low: usize, close: usize },
    AllValuesNaN,
    InvalidLength { length: usize, data_len: usize },
    NotEnoughValidData { needed: usize, valid: usize },
    InvalidMult { mult: f64 },
    InvalidAlpha { alpha: f64 },
    InvalidBeta { beta: f64 },
    DegenerateKernel { alpha: f64, beta: f64 },
    OutputLengthMismatch { expected: usize, got: usize },
    InvalidRange { start: String, end: String, step: String },
    InvalidKernelForBatch(Kernel),
}

Python Bindings

Python exposes slice-based single-run and batch functions plus a streaming class. The single-run binding returns a dictionary of NumPy arrays for every output family. Batch returns 2D arrays for each output together with the parameter axes and matrix dimensions. The stream returns a ten-value tuple once the full warmup has completed.

import numpy as np
from vector_ta import (
    adjustable_ma_alternating_extremities,
    adjustable_ma_alternating_extremities_batch,
    AdjustableMaAlternatingExtremitiesStream,
)

high = np.asarray(high_values, dtype=np.float64)
low = np.asarray(low_values, dtype=np.float64)
close = np.asarray(close_values, dtype=np.float64)

result = adjustable_ma_alternating_extremities(
    high,
    low,
    close,
    length=50,
    mult=2.0,
    alpha=1.0,
    beta=0.5,
    kernel="auto",
)

print(result["ma"][-1], result["extremity"][-1], result["state"][-1])

batch = adjustable_ma_alternating_extremities_batch(
    high,
    low,
    close,
    length_range=(30, 50, 10),
    mult_range=(1.5, 2.5, 0.5),
    alpha_range=(0.8, 1.2, 0.2),
    beta_range=(0.3, 0.7, 0.2),
    kernel="auto",
)

stream = AdjustableMaAlternatingExtremitiesStream(50, 2.0, 1.0, 0.5)
bar = stream.update(high[-1], low[-1], close[-1])

JavaScript/WASM Bindings

The WASM surface exposes one function for a normal run and one for batch sweeps. Both accept JavaScript typed arrays and return plain objects. The single-run call returns the ten output arrays. The batch call returns the flattened matrices plus the parameter lists and the final rows / cols shape metadata needed to reshape them on the host side.

import init, {
  adjustable_ma_alternating_extremities_js,
  adjustable_ma_alternating_extremities_batch,
} from "/pkg/vector_ta.js";

await init();

const high = new Float64Array(highValues);
const low = new Float64Array(lowValues);
const close = new Float64Array(closeValues);

const result = adjustable_ma_alternating_extremities_js(
  high,
  low,
  close,
  50,
  2.0,
  1.0,
  0.5
);

console.log(result.ma.at(-1), result.upper.at(-1), result.extremity.at(-1));

const batch = adjustable_ma_alternating_extremities_batch(high, low, close, {
  length_range: [30, 50, 10],
  mult_range: [1.5, 2.5, 0.5],
  alpha_range: [0.8, 1.2, 0.2],
  beta_range: [0.3, 0.7, 0.2],
});

console.log(batch.rows, batch.cols, batch.lengths, batch.ma.slice(0, 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