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 vectorta::indicators::rsmk::{rsmk, RsmkInput, RsmkParams};
use vectorta::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 vectorta::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 vectorta 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 vectorta 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 vectorta 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 support for RSMK is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.

# Coming soon: CUDA-accelerated RSMK calculations
# from vectorta import rsmk_cuda_batch, rsmk_cuda_many_series_one_param

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));
}

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators