use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
use crate::indicators::smoothing::EMA;
use crate::traits::Next;
use std::collections::VecDeque;
pub const METADATA: IndicatorMetadata = IndicatorMetadata {
name: "Relative Strength Markos Katsanos",
description: "An improved relative strength indicator that compares a security to a benchmark, separating periods of strong and weak relative performance.",
usage: "Use as a momentum-based relative strength indicator. Values above zero indicate the security is outperforming the benchmark over the specified period.",
keywords: &["relative strength", "momentum", "benchmark", "katsanos"],
ehlers_summary: "RSMK calculates the log-ratio momentum of a security relative to a benchmark (e.g., SPY). It measures the difference between current log-relative strength and its value N bars ago, then smooths it with an EMA. This approach identifies trends in relative performance with less lag than traditional methods.",
params: &[
ParamDef {
name: "length",
default: "90",
description: "Momentum lookback period",
},
ParamDef {
name: "ema_length",
default: "3",
description: "EMA smoothing period",
},
],
formula_source: "TASC March 2020",
formula_latex: r#"
\[
RSMK = EMA(\ln(\frac{P_t}{B_t}) - \ln(\frac{P_{t-n}}{B_{t-n}}), m) \times 100
\]
"#,
gold_standard_file: "rsmk_90_3.json",
category: "Momentum",
};
#[derive(Debug, Clone)]
pub struct RSMK {
length: usize,
ema: EMA,
log_val_window: VecDeque<f64>,
}
impl RSMK {
pub fn new(length: usize, ema_length: usize) -> Self {
Self {
length,
ema: EMA::new(ema_length),
log_val_window: VecDeque::with_capacity(length + 1),
}
}
}
impl Next<(f64, f64)> for RSMK {
type Output = f64;
fn next(&mut self, (price, benchmark): (f64, f64)) -> Self::Output {
if price <= 0.0 || benchmark <= 0.0 {
return 0.0;
}
let log_val = (price / benchmark).ln();
self.log_val_window.push_back(log_val);
if self.log_val_window.len() <= self.length {
return 0.0;
}
let old_log_val = self.log_val_window.pop_front().unwrap();
let momentum = log_val - old_log_val;
self.ema.next(momentum) * 100.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rsmk_basic() {
let mut rsmk = RSMK::new(2, 3);
assert_eq!(rsmk.next((10.0, 100.0)), 0.0);
assert_eq!(rsmk.next((11.0, 100.0)), 0.0);
let val = rsmk.next((12.0, 100.0));
approx::assert_relative_eq!(val, 18.232155, epsilon = 1e-6);
let val = rsmk.next((12.0, 110.0));
approx::assert_relative_eq!(val, 8.7011, epsilon = 1e-4);
}
}