market_model/strategy/
rsi.rs1use crate::strategy::PriceStrategy;
2use crate::types::Signal;
3
4pub 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 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 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}