Skip to main content

market_model/strategy/
rsi.rs

1use crate::strategy::PriceStrategy;
2use crate::types::Signal;
3
4/// RSI-based strategy.
5///
6/// Long when RSI drops below the oversold threshold,
7/// short when RSI rises above the overbought threshold.
8pub struct RsiStrategy {
9    pub window: usize,
10    pub oversold: f64,
11    pub overbought: f64,
12}
13
14impl RsiStrategy {
15    pub fn new(window: usize, oversold: f64, overbought: f64) -> Self {
16        assert!(window > 1);
17        assert!(oversold < overbought);
18        Self {
19            window,
20            oversold,
21            overbought,
22        }
23    }
24}
25
26impl PriceStrategy for RsiStrategy {
27    fn signal(&self, history: &[f64]) -> Signal {
28        if history.len() < self.window + 1 {
29            return Signal::Flat;
30        }
31        let rsi = compute_rsi(history, self.window);
32        if rsi < self.oversold {
33            Signal::Long
34        } else if rsi > self.overbought {
35            Signal::Short
36        } else {
37            Signal::Flat
38        }
39    }
40}
41
42fn compute_rsi(history: &[f64], window: usize) -> f64 {
43    let n = history.len();
44    let changes: Vec<f64> = history[n - window - 1..]
45        .windows(2)
46        .map(|w| w[1] - w[0])
47        .collect();
48
49    let avg_gain: f64 = changes
50        .iter()
51        .map(|&c| if c > 0.0 { c } else { 0.0 })
52        .sum::<f64>()
53        / window as f64;
54    let avg_loss: f64 = changes
55        .iter()
56        .map(|&c| if c < 0.0 { -c } else { 0.0 })
57        .sum::<f64>()
58        / window as f64;
59
60    if avg_loss == 0.0 {
61        100.0
62    } else {
63        100.0 - 100.0 / (1.0 + avg_gain / avg_loss)
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn test_rsi_oversold() {
73        let s = RsiStrategy::new(5, 30.0, 70.0);
74        // All price decreases: RSI near 0.
75        let history: Vec<f64> = (0..7).map(|i| 100.0 - i as f64).collect();
76        assert_eq!(s.signal(&history), Signal::Long);
77    }
78
79    #[test]
80    fn test_rsi_overbought() {
81        let s = RsiStrategy::new(5, 30.0, 70.0);
82        // All price increases: RSI near 100.
83        let history: Vec<f64> = (0..7).map(|i| 100.0 + i as f64).collect();
84        assert_eq!(s.signal(&history), Signal::Short);
85    }
86
87    #[test]
88    fn test_rsi_insufficient_data() {
89        let s = RsiStrategy::new(14, 30.0, 70.0);
90        assert_eq!(s.signal(&[10.0, 11.0]), Signal::Flat);
91    }
92}