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 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; 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 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
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05