Dynamic Momentum Index

Parameters: rsi_period = 14 | volatility_period = 5 | volatility_sma_period = 10 | upper_limit = 30 | lower_limit = 5

Overview

Dynamic Momentum Index starts from the same intuition as RSI, but it does not keep the RSI lookback fixed. The indicator first measures close-to-close volatility over a short rolling window and smooths that volatility over a second window. It then compares the current volatility reading against the smoothed average to decide how long the effective RSI lookback should be for the current bar.

When current volatility rises above its smoothed baseline, the effective RSI period contracts toward the lower limit so the oscillator reacts faster. When volatility falls below its smoothed baseline, the effective period expands toward the upper limit so the oscillator becomes slower and steadier. The result is still a familiar 0-to-100 RSI-style momentum line, but one whose responsiveness adapts to the volatility regime instead of remaining locked to a single static length.

Defaults: Dynamic Momentum Index uses `rsi_period = 14`, `volatility_period = 5`, `volatility_sma_period = 10`, `upper_limit = 30`, and `lower_limit = 5`.

Implementation Examples

Compute the volatility-adaptive RSI from a raw close slice or from candle closes.

use vector_ta::indicators::dynamic_momentum_index::{
    dynamic_momentum_index,
    DynamicMomentumIndexInput,
    DynamicMomentumIndexParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

let output = dynamic_momentum_index(&DynamicMomentumIndexInput::from_slice(
    &close,
    DynamicMomentumIndexParams {
        rsi_period: Some(14),
        volatility_period: Some(5),
        volatility_sma_period: Some(10),
        upper_limit: Some(30),
        lower_limit: Some(5),
    },
))?;

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

println!("latest DMI = {:?}", output.values.last());
println!("series length = {}", candle_output.values.len());

API Reference

Input Methods
// From candles and a named source field
DynamicMomentumIndexInput::from_candles(
    &Candles,
    &str,
    DynamicMomentumIndexParams,
) -> DynamicMomentumIndexInput

// From a raw slice
DynamicMomentumIndexInput::from_slice(&[f64], DynamicMomentumIndexParams)
    -> DynamicMomentumIndexInput

// From candles with default parameters
DynamicMomentumIndexInput::with_default_candles(&Candles)
    -> DynamicMomentumIndexInput
Parameters Structure
pub struct DynamicMomentumIndexParams {
    pub rsi_period: Option<usize>,            // default 14
    pub volatility_period: Option<usize>,     // default 5
    pub volatility_sma_period: Option<usize>, // default 10
    pub upper_limit: Option<usize>,           // default 30
    pub lower_limit: Option<usize>,           // default 5
}
Output Structure
pub struct DynamicMomentumIndexOutput {
    pub values: Vec<f64>,
}
Validation, Warmup & NaNs
  • The input slice must be non-empty and contain at least one finite value.
  • All five integer parameters must be greater than zero.
  • lower_limit must be less than or equal to upper_limit.
  • The longest contiguous finite run must be at least max(volatility_period + volatility_sma_period - 2, lower_limit) + 1 bars.
  • Warmup lasts for max(volatility_period + volatility_sma_period - 2, lower_limit) bars, which is also what the streaming API reports through get_warmup_period().
  • Streaming resets on non-finite input and returns None until both the volatility and adaptive RSI windows are rebuilt.
  • If the volatility ratio becomes unusable because current or average volatility is non-finite or non-positive, the adaptive RSI period falls back to upper_limit.
  • If both gains and losses sum to zero over the adaptive RSI window, the oscillator resolves to 50.0.
Builder, Streaming & Batch APIs
// Builder
DynamicMomentumIndexBuilder::new()
    .rsi_period(usize)
    .volatility_period(usize)
    .volatility_sma_period(usize)
    .upper_limit(usize)
    .lower_limit(usize)
    .kernel(Kernel)
    .apply_slice(&[f64])

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

DynamicMomentumIndexBuilder::new()
    .into_stream()

// Stream
DynamicMomentumIndexStream::try_new(DynamicMomentumIndexParams)
DynamicMomentumIndexStream::update(f64) -> Option<f64>
DynamicMomentumIndexStream::reset()
DynamicMomentumIndexStream::get_warmup_period() -> usize

// Batch
DynamicMomentumIndexBatchBuilder::new()
    .rsi_period_range(start, end, step)
    .volatility_period_range(start, end, step)
    .volatility_sma_period_range(start, end, step)
    .upper_limit_range(start, end, step)
    .lower_limit_range(start, end, step)
    .kernel(Kernel)
    .apply_slice(&[f64])

DynamicMomentumIndexBatchBuilder::new()
    .apply_candles(&Candles)
Error Handling
pub enum DynamicMomentumIndexError {
    EmptyInputData,
    AllValuesNaN,
    InvalidRsiPeriod { rsi_period: usize },
    InvalidVolatilityPeriod { volatility_period: usize },
    InvalidVolatilitySmaPeriod { volatility_sma_period: usize },
    InvalidUpperLimit { upper_limit: usize },
    InvalidLowerLimit { lower_limit: usize },
    InvalidLimits { lower_limit: usize, upper_limit: usize },
    NotEnoughValidData { needed: usize, valid: usize },
    OutputLengthMismatch { expected: usize, got: usize },
    InvalidRange { start: usize, end: usize, step: usize },
    InvalidKernelForBatch(Kernel),
    MismatchedOutputLen { dst_len: usize, expected_len: usize },
    InvalidInput { msg: String },
}

Python Bindings

Python exposes an array-returning single-run function, a streaming class, and a batch function. The single-run binding returns one NumPy array of adaptive RSI values. Batch returns the oscillator matrix plus the tested parameter axes and the final rows and cols shape.

import numpy as np
from vector_ta import (
    dynamic_momentum_index,
    dynamic_momentum_index_batch,
    DynamicMomentumIndexStream,
)

data = np.asarray(close_values, dtype=np.float64)

values = dynamic_momentum_index(
    data,
    rsi_period=14,
    volatility_period=5,
    volatility_sma_period=10,
    upper_limit=30,
    lower_limit=5,
    kernel="auto",
)

stream = DynamicMomentumIndexStream(
    rsi_period=14,
    volatility_period=5,
    volatility_sma_period=10,
    upper_limit=30,
    lower_limit=5,
)
print(stream.update(data[-1]))
print(stream.warmup_period)

batch = dynamic_momentum_index_batch(
    data,
    rsi_period_range=(10, 20, 5),
    volatility_period_range=(5, 10, 5),
    volatility_sma_period_range=(10, 20, 10),
    upper_limit_range=(20, 30, 10),
    lower_limit_range=(5, 10, 5),
    kernel="auto",
)

print(batch["rsi_periods"], batch["upper_limits"], batch["rows"], batch["cols"])

JavaScript/WASM Bindings

The WASM layer exposes a direct array-returning single-run wrapper, an object-returning batch wrapper, and lower-level allocation and in-place exports. The standard JavaScript path returns the adaptive RSI array, while the batch wrapper returns the flattened values matrix, tested combos, and the rows and cols shape.

import init, {
  dynamic_momentum_index_js,
  dynamic_momentum_index_batch_js,
} from "/pkg/vector_ta.js";

await init();

const data = new Float64Array(closeValues);

const values = dynamic_momentum_index_js(data, 14, 5, 10, 30, 5);
console.log(values);

const batch = dynamic_momentum_index_batch_js(data, {
  rsi_period_range: [10, 20, 5],
  volatility_period_range: [5, 10, 5],
  volatility_sma_period_range: [10, 20, 10],
  upper_limit_range: [20, 30, 10],
  lower_limit_range: [5, 10, 5],
});

console.log(batch.values, batch.combos, 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