Dynamic Momentum Index
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_limitmust be less than or equal toupper_limit.- The longest contiguous finite run must be at least
max(volatility_period + volatility_sma_period - 2, lower_limit) + 1bars. - Warmup lasts for
max(volatility_period + volatility_sma_period - 2, lower_limit)bars, which is also what the streaming API reports throughget_warmup_period(). - Streaming resets on non-finite input and returns
Noneuntil 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
Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)