Skip to content

Commit 8b4fc14

Browse files
authored
✨ TRIX, TRIX signal 지표 추가 (#23)
* ✨ TRIX 지표 추가와 패치 버전 갱신 * ♻️ TRIX 계산에 기존 EMA 함수 사용 * 🧪 TRIX 테스트 블록 정리 * ♻️ Polars TRIX 중복 API 제거
1 parent 6c974ae commit 8b4fc14

11 files changed

Lines changed: 2621 additions & 2 deletions

File tree

core/src/indicators/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ mod sonar;
3737
mod stochf;
3838
mod stochrsi;
3939
mod stochs;
40+
mod trix;
4041
mod ultosc;
4142
mod vr;
4243
mod willr;
@@ -81,6 +82,7 @@ pub use sonar::*;
8182
pub use stochf::*;
8283
pub use stochrsi::*;
8384
pub use stochs::*;
85+
pub use trix::*;
8486
pub use ultosc::*;
8587
pub use vr::*;
8688
pub use willr::*;

core/src/indicators/trix.rs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
use crate::indicators::ema::ema;
2+
3+
pub fn trix(
4+
data: &[Option<f64>],
5+
period: usize,
6+
signal_period: usize,
7+
) -> (Vec<Option<f64>>, Vec<Option<f64>>) {
8+
let line = trix_line(data, period);
9+
let signal = ema(&line, signal_period);
10+
11+
(line, signal)
12+
}
13+
14+
pub fn trix_line(data: &[Option<f64>], period: usize) -> Vec<Option<f64>> {
15+
let ema_values = ema(data, period);
16+
let double_ema = ema(&ema_values, period);
17+
let triple_ema = ema(&double_ema, period);
18+
19+
triple_ema
20+
.iter()
21+
.enumerate()
22+
.map(|(idx, &current)| {
23+
if idx == 0 {
24+
return None;
25+
}
26+
27+
match (current, triple_ema[idx - 1]) {
28+
(Some(current), Some(previous)) if previous != 0.0 => {
29+
Some((current - previous) * 100.0 / previous)
30+
}
31+
_ => None,
32+
}
33+
})
34+
.collect()
35+
}
36+
37+
pub fn trix_signal(data: &[Option<f64>], period: usize, signal_period: usize) -> Vec<Option<f64>> {
38+
let line = trix_line(data, period);
39+
ema(&line, signal_period)
40+
}
41+
42+
#[cfg(test)]
43+
mod tests {
44+
use super::*;
45+
use crate::indicators::ema::ema;
46+
use crate::testutils;
47+
use crate::utils::round_vec;
48+
49+
#[test]
50+
fn test_trix() {
51+
// Given
52+
let test_cases = vec!["005930", "TSLA"];
53+
54+
// When
55+
for symbol in test_cases {
56+
let input = testutils::load_data(&format!("../data/{}.json", symbol), "c")
57+
.into_iter()
58+
.map(Some)
59+
.collect::<Vec<_>>();
60+
let (line, signal) = trix(&input, 12, 9);
61+
62+
let expected_line = testutils::load_expected::<Option<f64>>(&format!(
63+
"../data/expected/trix_line_{}.json",
64+
symbol
65+
));
66+
let expected_signal = testutils::load_expected::<Option<f64>>(&format!(
67+
"../data/expected/trix_signal_{}.json",
68+
symbol
69+
));
70+
71+
// Then
72+
assert_eq!(
73+
round_vec(line, 8),
74+
round_vec(expected_line, 8),
75+
"TRIX line test failed for symbol {}.",
76+
symbol
77+
);
78+
assert_eq!(
79+
round_vec(signal, 8),
80+
round_vec(expected_signal, 8),
81+
"TRIX signal test failed for symbol {}.",
82+
symbol
83+
);
84+
}
85+
}
86+
87+
#[test]
88+
fn test_trix_matches_composed_ema_across_gaps() {
89+
// Given
90+
let input = vec![
91+
Some(1.0),
92+
Some(2.0),
93+
Some(3.0),
94+
Some(4.0),
95+
None,
96+
Some(5.0),
97+
Some(6.0),
98+
Some(7.0),
99+
Some(8.0),
100+
Some(9.0),
101+
];
102+
103+
// When
104+
let line = trix_line(&input, 2);
105+
let single = ema(&input, 2);
106+
let double = ema(&single, 2);
107+
let triple = ema(&double, 2);
108+
let expected = triple
109+
.iter()
110+
.enumerate()
111+
.map(|(idx, &current)| {
112+
if idx == 0 {
113+
return None;
114+
}
115+
116+
match (current, triple[idx - 1]) {
117+
(Some(current), Some(previous)) if previous != 0.0 => {
118+
Some((current - previous) * 100.0 / previous)
119+
}
120+
_ => None,
121+
}
122+
})
123+
.collect::<Vec<_>>();
124+
125+
// Then
126+
assert_eq!(round_vec(line, 8), round_vec(expected, 8));
127+
}
128+
129+
#[test]
130+
fn test_trix_signal_follows_base_ema_contract_across_gaps() {
131+
// Given
132+
let input = vec![
133+
Some(1.0),
134+
Some(2.0),
135+
Some(3.0),
136+
Some(4.0),
137+
None,
138+
Some(5.0),
139+
Some(6.0),
140+
Some(7.0),
141+
Some(8.0),
142+
Some(9.0),
143+
];
144+
145+
// When
146+
let (line, signal) = trix(&input, 2, 2);
147+
148+
// Then
149+
assert_eq!(signal, ema(&line, 2));
150+
assert_eq!(signal, trix_signal(&input, 2, 2));
151+
}
152+
153+
#[test]
154+
fn test_trix_returns_none_when_previous_triple_ema_is_zero() {
155+
// Given
156+
let input = vec![Some(0.0), Some(0.0), Some(1.0), Some(2.0)];
157+
158+
// When
159+
let line = trix_line(&input, 1);
160+
161+
// Then
162+
assert_eq!(line, vec![None, None, None, Some(100.0)]);
163+
}
164+
}

0 commit comments

Comments
 (0)