Rate of Change (ROC)

Parameters: period = 9

Overview

The Rate of Change (ROC) calculates momentum by measuring the percentage difference between the current price and the price from n periods earlier. The formula subtracts the historical price from the current value, divides by the historical price, and multiplies by 100 to express the result as a percentage. ROC oscillates around a zero centerline, with positive values signaling upward momentum and negative values indicating downward pressure. Unlike moving averages that smooth price action, ROC directly quantifies velocity of price movement without lag or smoothing. Traders employ ROC to identify momentum divergences where price makes new highs or lows while ROC fails to confirm, suggesting potential trend exhaustion. Extreme ROC readings often coincide with overbought or oversold conditions, though specific threshold levels vary by instrument and timeframe. The default 9-period lookback captures short term momentum shifts while filtering single-bar noise, though longer periods reveal more persistent trends at the cost of reduced sensitivity.

Implementation Examples

Get started with ROC in just a few lines:

use vectorta::indicators::roc::{roc, RocInput, RocParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with price data slice
let prices = vec![100.0, 99.0, 101.5, 103.0, 105.0, 104.5];
let params = RocParams { period: Some(9) };
let input = RocInput::from_slice(&prices, params);
let result = roc(&input)?;

// Using with Candles data structure
// Quick and simple with default parameters (period=9; source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = RocInput::with_default_candles(&candles);
let result = roc(&input)?;

// Access the ROC values (percentage)
for value in result.values {
    println!("ROC: {}", value);
}

API Reference

Input Methods
// From price slice
RocInput::from_slice(&[f64], RocParams) -> RocInput

// From candles with custom source
RocInput::from_candles(&Candles, &str, RocParams) -> RocInput

// From candles with default params (close prices, period=9)
RocInput::with_default_candles(&Candles) -> RocInput
Parameters Structure
pub struct RocParams {
    pub period: Option<usize>, // Default: 9
}
Output Structure
pub struct RocOutput {
    pub values: Vec<f64>, // ROC values in percent with NaN warmup prefix
}
Validation, Warmup & NaNs
  • period > 0; otherwise RocError::InvalidPeriod.
  • Requires at least period valid points after the first finite input; else RocError::NotEnoughValidData.
  • If all inputs are NaNRocError::AllValuesNaN; empty slice → RocError::EmptyData.
  • Warmup: indices before first_finite_index + period are NaN (batch); streaming returns None until ready.
  • When Pt−n == 0 or NaN, output is 0.0 for that index (defined behavior).
Error Handling
use vectorta::indicators::roc::{roc, RocError};

match roc(&input) {
    Ok(output) => process_results(output.values),
    Err(RocError::EmptyData) =>
        eprintln!("Input data is empty"),
    Err(RocError::InvalidPeriod { period, data_len }) =>
        eprintln!("Invalid period {} for data length {}", period, data_len),
    Err(RocError::NotEnoughValidData { needed, valid }) =>
        eprintln!("Need {} valid points, only {} available", needed, valid),
    Err(RocError::AllValuesNaN) =>
        eprintln!("All input values are NaN"),
}

Python Bindings

Basic Usage

Calculate ROC using NumPy arrays:

import numpy as np
from vectorta import roc

# Prepare price data as NumPy array
prices = np.array([100.0, 99.0, 101.5, 103.0, 105.0, 104.5])

# Calculate ROC (period is required)
result = roc(prices, period=9)

# Specify kernel (optional): "auto", "scalar", "avx2", "avx512" (if available)
result = roc(prices, period=9, kernel="auto")

# Result is a NumPy array matching input length
print(f"ROC values: {result}")
Streaming Real-time Updates

Process real-time price updates efficiently:

from vectorta import RocStream

# Initialize streaming ROC calculator
stream = RocStream(period=9)

# Process real-time price updates
for price in price_feed:
    roc_value = stream.update(price)
    if roc_value is not None:
        print(f"Current ROC: {roc_value}")
Batch Parameter Optimization

Test multiple period values for optimization:

import numpy as np
from vectorta import roc_batch

prices = np.array([...])

# Define period range as (start, end, step)
period_range = (5, 20, 5)  # 5, 10, 15, 20

results = roc_batch(
    prices,
    period_range=period_range,
    kernel="auto",
)

print(f"Values shape: {results['values'].shape}")  # (num_periods, len(prices))
print(f"Periods tested: {results['periods']}")
CUDA Acceleration

CUDA support for ROC is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.

# Coming soon: CUDA-accelerated ROC calculations
# See other indicators for expected API shape and examples.

JavaScript/WASM Bindings

Basic Usage

Calculate ROC in JavaScript/TypeScript:

import { roc_js } from 'vectorta-wasm';

// Price data as Float64Array or regular array
const prices = new Float64Array([100, 99, 101.5, 103, 105, 104.5]);

// Calculate ROC with specified period
const result = roc_js(prices, 9);

// Result is a Float64Array
console.log('ROC values:', result);
Memory-Efficient Operations

Use zero-copy operations for better performance with large datasets:

import { roc_alloc, roc_free, roc_into, memory } from 'vectorta-wasm';

const prices = new Float64Array([/* your data */]);
const length = prices.length;

// Allocate WASM memory for input and output
const inPtr = roc_alloc(length);
const outPtr = roc_alloc(length);

// Copy input data into WASM memory
new Float64Array(memory.buffer, inPtr, length).set(prices);

// Calculate ROC directly into allocated memory
// Args: in_ptr, out_ptr, len, period
roc_into(inPtr, outPtr, length, 9);

// Read results from WASM memory (slice() to copy out)
const rocValues = new Float64Array(memory.buffer, outPtr, length).slice();

// Free allocated memory when done
roc_free(inPtr, length);
roc_free(outPtr, length);

console.log('ROC values:', rocValues);
Batch Processing

Test multiple period values efficiently:

import { roc_batch } from 'vectorta-wasm';

const prices = new Float64Array([/* historical prices */]);

// Run batch calculation with a period range
const output = roc_batch(prices, { period_range: [5, 20, 5] });

// Output fields:
//  - output.values: flat array of length rows*cols (row-major)
//  - output.rows: number of period values tested
//  - output.cols: length of prices
//  - output.combos: array of { period: number | null } used

// Reshape values into 2D [rows x cols]
const matrix = [];
for (let r = 0; r < output.rows; r++) {
  const start = r * output.cols;
  matrix.push(output.values.slice(start, start + output.cols));
}

// List tested periods
const testedPeriods = output.combos.map(c => c.period ?? 9);
console.log('Periods:', testedPeriods);
console.log('First combo ROC series length:', matrix[0].length);

Performance Analysis

Comparison:
View:

Across sizes, Rust CPU runs about 7.38× faster than Tulip C in this benchmark.

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05

Related Indicators