quantwave_core/indicators/
rsmk.rs1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::indicators::smoothing::EMA;
3use crate::traits::Next;
4use std::collections::VecDeque;
5
6pub const METADATA: IndicatorMetadata = IndicatorMetadata {
7 name: "Relative Strength Markos Katsanos",
8 description: "An improved relative strength indicator that compares a security to a benchmark, separating periods of strong and weak relative performance.",
9 usage: "Use as a momentum-based relative strength indicator. Values above zero indicate the security is outperforming the benchmark over the specified period.",
10 keywords: &["relative strength", "momentum", "benchmark", "katsanos"],
11 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.",
12 params: &[
13 ParamDef {
14 name: "length",
15 default: "90",
16 description: "Momentum lookback period",
17 },
18 ParamDef {
19 name: "ema_length",
20 default: "3",
21 description: "EMA smoothing period",
22 },
23 ],
24 formula_source: "TASC March 2020",
25 formula_latex: r#"
26\[
27RSMK = EMA(\ln(\frac{P_t}{B_t}) - \ln(\frac{P_{t-n}}{B_{t-n}}), m) \times 100
28\]
29"#,
30 gold_standard_file: "rsmk_90_3.json",
31 category: "Momentum",
32};
33
34#[derive(Debug, Clone)]
38pub struct RSMK {
39 length: usize,
40 ema: EMA,
41 log_val_window: VecDeque<f64>,
42}
43
44impl RSMK {
45 pub fn new(length: usize, ema_length: usize) -> Self {
46 Self {
47 length,
48 ema: EMA::new(ema_length),
49 log_val_window: VecDeque::with_capacity(length + 1),
50 }
51 }
52}
53
54impl Next<(f64, f64)> for RSMK {
55 type Output = f64;
56
57 fn next(&mut self, (price, benchmark): (f64, f64)) -> Self::Output {
58 if price <= 0.0 || benchmark <= 0.0 {
59 return 0.0;
62 }
63
64 let log_val = (price / benchmark).ln();
65 self.log_val_window.push_back(log_val);
66
67 if self.log_val_window.len() <= self.length {
68 return 0.0;
73 }
74
75 let old_log_val = self.log_val_window.pop_front().unwrap();
76 let momentum = log_val - old_log_val;
77
78 self.ema.next(momentum) * 100.0
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn test_rsmk_basic() {
88 let mut rsmk = RSMK::new(2, 3);
89
90 assert_eq!(rsmk.next((10.0, 100.0)), 0.0);
95
96 assert_eq!(rsmk.next((11.0, 100.0)), 0.0);
98
99 let val = rsmk.next((12.0, 100.0));
104 approx::assert_relative_eq!(val, 18.232155, epsilon = 1e-6);
105
106 let val = rsmk.next((12.0, 110.0));
113 approx::assert_relative_eq!(val, 8.7011, epsilon = 1e-4);
114 }
115}