Relative Strength Mark (RSMK)
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; otherwiseRsmkError::EmptyDataorRsmkError::InvalidPeriod.lookback > 0,period > 0,signal_period > 0; alsolookback < len, andperiod,signal_period≤len.- First finite log-ratio index is detected; require at least
lookback + max(period, signal_period)valid points after it, elseRsmkError::NotEnoughValidData. - If no finite log-ratio exists (all NaN or
compare == 0), returnsRsmkError::AllValuesNaN. - Warmup: prefix values are
NaNup 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
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-08