Relative Strength Mark (RSMK)

Parameters: lookback = 90 | period = 3 | signal_period = 20 | matype = ema | signal_matype = ema

Overview

The Relative Strength Mark compares the performance of one security against a benchmark by transforming their price ratio into a momentum oscillator with signal line. RSMK begins by taking the natural logarithm of the main series divided by the comparison series, then applies a momentum transformation over the lookback period to capture rate of change. This log momentum gets smoothed using a moving average to create the indicator line, while a second moving average of that line produces the signal for crossover detection. The logarithmic transformation makes the comparison symmetric, meaning outperformance and underperformance receive equal weight regardless of magnitude. Traders use RSMK to identify when an asset is strengthening or weakening relative to its benchmark, with the indicator above zero showing relative outperformance and below zero indicating underperformance. Crossovers between the indicator and signal lines generate trading signals, while divergences between RSMK and price can warn of impending reversals in relative strength.

Defaults: lookback=90, period=3, signal_period=20, matype="ema", signal_matype="ema".

Implementation Examples

Get started with RSMK on arrays or candles:

use vector_ta::indicators::rsmk::{rsmk, RsmkInput, RsmkParams};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with slices
let main = vec![100.0, 101.0, 102.0, 103.0];
let compare = vec![100.0, 100.5, 101.0, 101.5];
let params = RsmkParams {
    lookback: Some(90),
    period: Some(3),
    signal_period: Some(20),
    matype: Some("ema".to_string()),
    signal_matype: Some("ema".to_string()),
};
let input = RsmkInput::from_slices(&main, &compare, params);
let out = rsmk(&input)?;

// Using with Candles (defaults: close, 90/3/20 EMA)
let candles_main: Candles = read_candles_from_csv("data/main.csv")?;
let candles_cmp: Candles = read_candles_from_csv("data/benchmark.csv")?;
let input = RsmkInput::with_default_candles(&candles_main, &candles_cmp);
let out = rsmk(&input)?;

// Access outputs
for (i, s) in out.indicator.iter().zip(out.signal.iter()) {
    println!("RSMK={}, Signal={}", i, s);
}

API Reference

Input Methods
// From slices (two equal-length arrays)
RsmkInput::from_slices(&[f64], &[f64], RsmkParams) -> RsmkInput

// From candles with custom source (e.g., "close")
RsmkInput::from_candles(&Candles, &Candles, &str, RsmkParams) -> RsmkInput

// From candles with defaults (source="close", 90/3/20 EMA)
RsmkInput::with_default_candles(&Candles, &Candles) -> RsmkInput
Parameters Structure
pub struct RsmkParams {
    pub lookback: Option<usize>,       // Default: 90
    pub period: Option<usize>,         // Default: 3
    pub signal_period: Option<usize>,  // Default: 20
    pub matype: Option<String>,        // Default: "ema"
    pub signal_matype: Option<String>, // Default: "ema"
}
Output Structure
pub struct RsmkOutput {
    pub indicator: Vec<f64>, // main RSMK line
    pub signal: Vec<f64>,    // signal line
}
Validation, Warmup & NaNs
  • main.len() == compare.len() and both non-empty; otherwise RsmkError::EmptyData or RsmkError::InvalidPeriod.
  • lookback > 0, period > 0, signal_period > 0; also lookback < len, and period, signal_periodlen.
  • First finite log-ratio index is detected; require at least lookback + max(period, signal_period) valid points after it, else RsmkError::NotEnoughValidData.
  • If no finite log-ratio exists (all NaN or compare == 0), returns RsmkError::AllValuesNaN.
  • Warmup: prefix values are NaN up to the indicator and signal warmups; signal warmup ≥ indicator warmup.
  • Invalid/unsupported MA types propagate as RsmkError::MaError(String) from the MA dispatcher.
Error Handling
use vector_ta::indicators::rsmk::{rsmk, RsmkError};

match rsmk(&input) {
    Ok(out) => handle(out.indicator, out.signal),
    Err(RsmkError::EmptyData) => eprintln!("empty input"),
    Err(RsmkError::InvalidPeriod { period, data_len }) => {
        eprintln!("invalid period {} for len {}", period, data_len);
    }
    Err(RsmkError::NotEnoughValidData { needed, valid }) => {
        eprintln!("need {} valid points, only {}", needed, valid);
    }
    Err(RsmkError::AllValuesNaN) => eprintln!("no finite log-ratio"),
    Err(RsmkError::MaError(msg)) => eprintln!("MA error: {}", msg),
}

Python Bindings

Basic Usage

Compute RSMK on NumPy arrays (two equal-length arrays):

import numpy as np
from vector_ta import rsmk

main = np.array([100.0, 101.0, 102.0], dtype=float)
compare = np.array([100.0, 100.5, 101.0], dtype=float)

indicator, signal = rsmk(
    main, compare,
    lookback=90, period=3, signal_period=20,
    matype="ema", signal_matype="ema",
    kernel="auto"
)
print(indicator.shape, signal.shape)
Streaming Real-time Updates
from vector_ta import RsmkStream

stream = RsmkStream(lookback=90, period=3, signal_period=20, matype="ema", signal_matype="ema")
for (m, c) in tick_pairs:
    val = stream.update(m, c)  # returns (indicator, signal) or None during warmup
    if val is not None:
        ind, sig = val
        handle(ind, sig)
Batch Parameter Sweep
import numpy as np
from vector_ta import rsmk_batch

main = np.array([...], dtype=float)
compare = np.array([...], dtype=float)

out = rsmk_batch(
    main, compare,
    lookback_range=(60, 120, 30),
    period_range=(2, 6, 1),
    signal_period_range=(10, 30, 10),
    matype="ema", signal_matype="ema",
    kernel="auto"
)

print(out['indicator'].shape)   # (rows, len)
print(out['signal'].shape)      # (rows, len)
print(out['lookbacks'])
print(out['periods'])
print(out['signal_periods'])
CUDA Acceleration

CUDA helpers are available when the Python package is built with CUDA support. Inputs must be float32; outputs are device arrays (DLPack / __cuda_array_interface__ compatible).

import numpy as np
from vector_ta import rsmk_cuda_batch_dev, rsmk_cuda_many_series_one_param_dev

# One series (float32)
main_f32 = np.asarray(load_series(), dtype=np.float32)
compare_f32 = np.asarray(load_series(), dtype=np.float32)

dev = rsmk_cuda_batch_dev(
    main_f32=main_f32,
    compare_f32=compare_f32,
    lookback_range=(2, 20, 2),
    period_range=(5, 30, 5),
    signal_period_range=(5, 30, 5),
    device_id=0,
)

# Many series (time-major)
main_tm_f32 = np.asarray(load_series_time_major_matrix(), dtype=np.float32)
compare_tm_f32 = np.asarray(load_series_time_major_matrix(), dtype=np.float32)
rows, cols = main_tm_f32.shape

dev_tm = rsmk_cuda_many_series_one_param_dev(
    main_tm_f32=main_tm_f32,
    compare_tm_f32=compare_tm_f32,
    cols=cols,
    rows=rows,
    lookback=14,
    period=14,
    signal_period=9,
    device_id=0,
)

JavaScript/WASM Bindings

Basic Usage

Calculate RSMK in JavaScript/TypeScript:

import { rsmk_js } from 'vectorta-wasm';

const main = new Float64Array([/* main series */]);
const compare = new Float64Array([/* benchmark series */]);

// Defaults are EMA types; provide explicit values if needed
const res = rsmk_js(main, compare, 90, 3, 20, 'ema', 'ema');

// Returned object: { values: Float64Array, rows: 2, cols: len }
const indicator = res.values.slice(0, res.cols);
const signal = res.values.slice(res.cols);
console.log(indicator, signal);
Memory-Efficient Operations

Use zero-copy operations for large arrays:

import { rsmk_alloc, rsmk_free, rsmk_into, memory } from 'vectorta-wasm';

const len = main.length;
const inPtr = rsmk_alloc(len);
const cmpPtr = rsmk_alloc(len);
const indPtr = rsmk_alloc(len);
const sigPtr = rsmk_alloc(len);

new Float64Array(memory.buffer, inPtr, len).set(main);
new Float64Array(memory.buffer, cmpPtr, len).set(compare);

// Compute directly into WASM memory
rsmk_into(inPtr, indPtr, sigPtr, len, cmpPtr, 90, 3, 20, 'ema', 'ema');

// Copy results out
const indicator = new Float64Array(memory.buffer, indPtr, len).slice();
const signal = new Float64Array(memory.buffer, sigPtr, len).slice();

// Free
rsmk_free(inPtr, len);
rsmk_free(cmpPtr, len);
rsmk_free(indPtr, len);
rsmk_free(sigPtr, len);
Batch Processing

Test multiple parameter combinations with a config object:

import { rsmk_batch } from 'vectorta-wasm';

const config = {
  lookback_range: [60, 120, 30],
  period_range: [2, 6, 1],
  signal_period_range: [10, 30, 10],
  matype: 'ema',
  signal_matype: 'ema',
};

const out = rsmk_batch(main, compare, config);
// out: { indicators: Float64Array, signals: Float64Array, combos: RsmkParams[], rows, cols }

// Reshape rows x cols as needed
const indicators = [];
const signals = [];
for (let r = 0; r < out.rows; r++) {
  const start = r * out.cols;
  indicators.push(out.indicators.slice(start, start + out.cols));
  signals.push(out.signals.slice(start, start + out.cols));
}

CUDA Bindings (Rust)

use vector_ta::cuda::CudaRsmk;
use vector_ta::indicators::rsmk::RsmkBatchRange;

let cuda = CudaRsmk::new(0)?;

let main_f32: [f32] = /* ... */;
let compare_f32: [f32] = /* ... */;
let sweep = RsmkBatchRange::default();

let out = cuda.rsmk_batch_dev(&main_f32, &compare_f32, &sweep)?;
let _ = out;

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators