@ebowwa/quant-rust 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +161 -0
  2. package/bun-ffi.d.ts +54 -0
  3. package/dist/index.js +576 -0
  4. package/dist/src/index.d.ts +324 -0
  5. package/dist/src/index.d.ts.map +1 -0
  6. package/dist/types/index.d.ts +403 -0
  7. package/dist/types/index.d.ts.map +1 -0
  8. package/native/README.md +62 -0
  9. package/native/darwin-arm64/libquant_rust.dylib +0 -0
  10. package/package.json +70 -0
  11. package/scripts/postinstall.cjs +85 -0
  12. package/src/ffi.rs +496 -0
  13. package/src/index.ts +1073 -0
  14. package/src/indicators/ma.rs +222 -0
  15. package/src/indicators/mod.rs +18 -0
  16. package/src/indicators/momentum.rs +353 -0
  17. package/src/indicators/sr.rs +195 -0
  18. package/src/indicators/trend.rs +351 -0
  19. package/src/indicators/volatility.rs +270 -0
  20. package/src/indicators/volume.rs +213 -0
  21. package/src/lib.rs +130 -0
  22. package/src/patterns/breakout.rs +431 -0
  23. package/src/patterns/chart.rs +772 -0
  24. package/src/patterns/mod.rs +394 -0
  25. package/src/patterns/sr.rs +423 -0
  26. package/src/prediction/amm.rs +338 -0
  27. package/src/prediction/arbitrage.rs +230 -0
  28. package/src/prediction/calibration.rs +317 -0
  29. package/src/prediction/kelly.rs +232 -0
  30. package/src/prediction/lmsr.rs +194 -0
  31. package/src/prediction/mod.rs +59 -0
  32. package/src/prediction/odds.rs +229 -0
  33. package/src/prediction/pnl.rs +254 -0
  34. package/src/prediction/risk.rs +228 -0
  35. package/src/risk/beta.rs +257 -0
  36. package/src/risk/drawdown.rs +256 -0
  37. package/src/risk/leverage.rs +201 -0
  38. package/src/risk/mod.rs +388 -0
  39. package/src/risk/portfolio.rs +287 -0
  40. package/src/risk/ratios.rs +290 -0
  41. package/src/risk/sizing.rs +194 -0
  42. package/src/risk/var.rs +222 -0
  43. package/src/stats/cdf.rs +257 -0
  44. package/src/stats/correlation.rs +225 -0
  45. package/src/stats/distribution.rs +194 -0
  46. package/src/stats/hypothesis.rs +177 -0
  47. package/src/stats/matrix.rs +346 -0
  48. package/src/stats/mod.rs +257 -0
  49. package/src/stats/regression.rs +239 -0
  50. package/src/stats/rolling.rs +193 -0
  51. package/src/stats/timeseries.rs +263 -0
  52. package/src/types.rs +224 -0
  53. package/src/utils/mod.rs +215 -0
  54. package/src/utils/normalize.rs +192 -0
  55. package/src/utils/price.rs +167 -0
  56. package/src/utils/quantiles.rs +177 -0
  57. package/src/utils/returns.rs +158 -0
  58. package/src/utils/rolling.rs +97 -0
  59. package/src/utils/stats.rs +154 -0
  60. package/types/index.ts +513 -0
@@ -0,0 +1,195 @@
1
+ //! Support and Resistance Indicators
2
+ //!
3
+ //! Tools for identifying key price levels.
4
+
5
+ /// Classic Pivot Points result
6
+ #[derive(Debug, Clone, Copy, PartialEq)]
7
+ pub struct PivotPointsResult {
8
+ /// Central pivot point
9
+ pub pivot: f64,
10
+ /// First resistance level
11
+ pub r1: f64,
12
+ /// Second resistance level
13
+ pub r2: f64,
14
+ /// Third resistance level
15
+ pub r3: f64,
16
+ /// First support level
17
+ pub s1: f64,
18
+ /// Second support level
19
+ pub s2: f64,
20
+ /// Third support level
21
+ pub s3: f64,
22
+ }
23
+
24
+ /// Fibonacci Retracement/Extension levels
25
+ #[derive(Debug, Clone, Copy, PartialEq)]
26
+ pub struct FibonacciLevels {
27
+ /// 0% level (swing high for retracement)
28
+ pub level0: f64,
29
+ /// 23.6% level
30
+ pub level236: f64,
31
+ /// 38.2% level
32
+ pub level382: f64,
33
+ /// 50% level
34
+ pub level500: f64,
35
+ /// 61.8% level (golden ratio)
36
+ pub level618: f64,
37
+ /// 78.6% level
38
+ pub level786: f64,
39
+ /// 100% level (swing low for retracement)
40
+ pub level1000: f64,
41
+ }
42
+
43
+ /// Classic Pivot Points
44
+ ///
45
+ /// Calculates support and resistance levels based on the
46
+ /// previous period's high, low, and close prices.
47
+ ///
48
+ /// # Formulas
49
+ /// - Pivot = (High + Low + Close) / 3
50
+ /// - R1 = 2 * Pivot - Low
51
+ /// - S1 = 2 * Pivot - High
52
+ /// - R2 = Pivot + (High - Low)
53
+ /// - S2 = Pivot - (High - Low)
54
+ /// - R3 = High + 2 * (Pivot - Low)
55
+ /// - S3 = Low - 2 * (High - Pivot)
56
+ ///
57
+ /// # Arguments
58
+ /// * `high` - Period high price
59
+ /// * `low` - Period low price
60
+ /// * `close` - Period close price
61
+ ///
62
+ /// # Returns
63
+ /// A PivotPointsResult with all pivot levels.
64
+ pub fn pivot_points(high: f64, low: f64, close: f64) -> PivotPointsResult {
65
+ let pivot = (high + low + close) / 3.0;
66
+
67
+ PivotPointsResult {
68
+ pivot,
69
+ r1: 2.0 * pivot - low,
70
+ r2: pivot + (high - low),
71
+ r3: high + 2.0 * (pivot - low),
72
+ s1: 2.0 * pivot - high,
73
+ s2: pivot - (high - low),
74
+ s3: low - 2.0 * (high - pivot),
75
+ }
76
+ }
77
+
78
+ /// Fibonacci Pivot Points
79
+ ///
80
+ /// Pivot points using Fibonacci ratios instead of the classic formula.
81
+ ///
82
+ /// # Arguments
83
+ /// * `high` - Period high price
84
+ /// * `low` - Period low price
85
+ /// * `close` - Period close price
86
+ ///
87
+ /// # Returns
88
+ /// A PivotPointsResult with Fibonacci-based levels.
89
+ pub fn fibonacci_pivots(high: f64, low: f64, close: f64) -> PivotPointsResult {
90
+ let pivot = (high + low + close) / 3.0;
91
+ let range = high - low;
92
+
93
+ PivotPointsResult {
94
+ pivot,
95
+ r1: pivot + 0.382 * range,
96
+ r2: pivot + 0.618 * range,
97
+ r3: pivot + 1.0 * range,
98
+ s1: pivot - 0.382 * range,
99
+ s2: pivot - 0.618 * range,
100
+ s3: pivot - 1.0 * range,
101
+ }
102
+ }
103
+
104
+ /// Fibonacci Retracement Levels
105
+ ///
106
+ /// Calculates retracement levels between a swing high and swing low.
107
+ /// These levels often act as support/resistance in trending markets.
108
+ ///
109
+ /// # Arguments
110
+ /// * `swing_high` - The swing high price (0% level)
111
+ /// * `swing_low` - The swing low price (100% level)
112
+ ///
113
+ /// # Returns
114
+ /// A FibonacciLevels struct with all retracement levels.
115
+ pub fn fibonacci_retracement(swing_high: f64, swing_low: f64) -> FibonacciLevels {
116
+ let diff = swing_high - swing_low;
117
+
118
+ FibonacciLevels {
119
+ level0: swing_high,
120
+ level236: swing_high - 0.236 * diff,
121
+ level382: swing_high - 0.382 * diff,
122
+ level500: swing_high - 0.5 * diff,
123
+ level618: swing_high - 0.618 * diff,
124
+ level786: swing_high - 0.786 * diff,
125
+ level1000: swing_low,
126
+ }
127
+ }
128
+
129
+ #[cfg(test)]
130
+ mod tests {
131
+ use super::*;
132
+
133
+ #[test]
134
+ fn test_pivot_points() {
135
+ let high = 110.0;
136
+ let low = 100.0;
137
+ let close = 105.0;
138
+
139
+ let result = pivot_points(high, low, close);
140
+
141
+ // Pivot = (110 + 100 + 105) / 3 = 105
142
+ assert!((result.pivot - 105.0).abs() < 1e-10);
143
+
144
+ // R1 = 2 * 105 - 100 = 110
145
+ assert!((result.r1 - 110.0).abs() < 1e-10);
146
+
147
+ // S1 = 2 * 105 - 110 = 100
148
+ assert!((result.s1 - 100.0).abs() < 1e-10);
149
+
150
+ // R2 = 105 + (110 - 100) = 115
151
+ assert!((result.r2 - 115.0).abs() < 1e-10);
152
+
153
+ // S2 = 105 - (110 - 100) = 95
154
+ assert!((result.s2 - 95.0).abs() < 1e-10);
155
+ }
156
+
157
+ #[test]
158
+ fn test_fibonacci_pivots() {
159
+ let high = 110.0;
160
+ let low = 100.0;
161
+ let close = 105.0;
162
+
163
+ let result = fibonacci_pivots(high, low, close);
164
+
165
+ // Pivot should be same as classic
166
+ assert!((result.pivot - 105.0).abs() < 1e-10);
167
+
168
+ // Range = 10
169
+ // R1 = 105 + 0.382 * 10 = 108.82
170
+ assert!((result.r1 - 108.82).abs() < 1e-10);
171
+
172
+ // S1 = 105 - 0.382 * 10 = 101.18
173
+ assert!((result.s1 - 101.18).abs() < 1e-10);
174
+ }
175
+
176
+ #[test]
177
+ fn test_fibonacci_retracement() {
178
+ let swing_high = 100.0;
179
+ let swing_low = 50.0;
180
+
181
+ let result = fibonacci_retracement(swing_high, swing_low);
182
+
183
+ // 0% level should be swing high
184
+ assert!((result.level0 - 100.0).abs() < 1e-10);
185
+
186
+ // 100% level should be swing low
187
+ assert!((result.level1000 - 50.0).abs() < 1e-10);
188
+
189
+ // 50% level
190
+ assert!((result.level500 - 75.0).abs() < 1e-10);
191
+
192
+ // 61.8% level (golden ratio)
193
+ assert!((result.level618 - 69.1).abs() < 0.1);
194
+ }
195
+ }
@@ -0,0 +1,351 @@
1
+ //! Trend Indicators
2
+ //!
3
+ //! Indicators that identify the direction and strength of market trends.
4
+
5
+ use crate::utils::{max, min};
6
+
7
+ /// ADX (Average Directional Index) result
8
+ #[derive(Debug, Clone, PartialEq)]
9
+ pub struct ADXResult {
10
+ /// ADX line (trend strength, 0-100)
11
+ pub adx: Vec<f64>,
12
+ /// +DI (positive directional indicator)
13
+ pub plus_di: Vec<f64>,
14
+ /// -DI (negative directional indicator)
15
+ pub minus_di: Vec<f64>,
16
+ }
17
+
18
+ /// Ichimoku Cloud result
19
+ #[derive(Debug, Clone, PartialEq)]
20
+ pub struct IchimokuResult {
21
+ /// Tenkan-sen (Conversion Line)
22
+ pub tenkan: Vec<f64>,
23
+ /// Kijun-sen (Base Line)
24
+ pub kijun: Vec<f64>,
25
+ /// Senkou Span A (Leading Span A)
26
+ pub senkou_a: Vec<f64>,
27
+ /// Senkou Span B (Leading Span B)
28
+ pub senkou_b: Vec<f64>,
29
+ /// Chikou Span (Lagging Span)
30
+ pub chikou: Vec<f64>,
31
+ }
32
+
33
+ /// Parabolic SAR result
34
+ #[derive(Debug, Clone, PartialEq)]
35
+ pub struct SARResult {
36
+ /// SAR values
37
+ pub sar: Vec<f64>,
38
+ /// Trend direction (true = uptrend, false = downtrend)
39
+ pub trend: Vec<bool>,
40
+ }
41
+
42
+ /// Average Directional Index (ADX)
43
+ ///
44
+ /// Measures the strength of a trend, regardless of direction.
45
+ /// ADX > 25 indicates a strong trend, < 20 indicates weak/no trend.
46
+ ///
47
+ /// # Arguments
48
+ /// * `high` - High prices
49
+ /// * `low` - Low prices
50
+ /// * `close` - Close prices
51
+ /// * `period` - ADX period (default: 14)
52
+ ///
53
+ /// # Returns
54
+ /// An ADXResult containing ADX, +DI, and -DI values.
55
+ pub fn adx(high: &[f64], low: &[f64], close: &[f64], period: usize) -> ADXResult {
56
+ if high.len() < period + 1 || period == 0 {
57
+ return ADXResult {
58
+ adx: Vec::new(),
59
+ plus_di: Vec::new(),
60
+ minus_di: Vec::new(),
61
+ };
62
+ }
63
+
64
+ // Calculate +DM and -DM
65
+ let mut plus_dm: Vec<f64> = vec![0.0];
66
+ let mut minus_dm: Vec<f64> = vec![0.0];
67
+
68
+ for i in 1..high.len() {
69
+ let up_move = high[i] - high[i - 1];
70
+ let down_move = low[i - 1] - low[i];
71
+
72
+ if up_move > down_move && up_move > 0.0 {
73
+ plus_dm.push(up_move);
74
+ minus_dm.push(0.0);
75
+ } else if down_move > up_move && down_move > 0.0 {
76
+ plus_dm.push(0.0);
77
+ minus_dm.push(down_move);
78
+ } else {
79
+ plus_dm.push(0.0);
80
+ minus_dm.push(0.0);
81
+ }
82
+ }
83
+
84
+ // Calculate ATR for DI calculation
85
+ let atr_result = super::atr(high, low, close, 1);
86
+ let tr = atr_result.tr;
87
+
88
+ // Smooth DM and TR using EMA
89
+ let smoothed_plus_dm = super::ema(&plus_dm, period);
90
+ let smoothed_minus_dm = super::ema(&minus_dm, period);
91
+ let smoothed_tr = super::ema(&tr, period);
92
+
93
+ // Calculate DI
94
+ let min_len = smoothed_plus_dm.len().min(smoothed_minus_dm.len()).min(smoothed_tr.len());
95
+
96
+ let mut plus_di: Vec<f64> = Vec::new();
97
+ let mut minus_di: Vec<f64> = Vec::new();
98
+
99
+ for i in 0..min_len {
100
+ if smoothed_tr[i] > 0.0 {
101
+ plus_di.push((smoothed_plus_dm[i] / smoothed_tr[i]) * 100.0);
102
+ minus_di.push((smoothed_minus_dm[i] / smoothed_tr[i]) * 100.0);
103
+ } else {
104
+ plus_di.push(0.0);
105
+ minus_di.push(0.0);
106
+ }
107
+ }
108
+
109
+ // Calculate DX and ADX
110
+ let mut dx: Vec<f64> = Vec::new();
111
+ for i in 0..plus_di.len() {
112
+ let di_sum = plus_di[i] + minus_di[i];
113
+ if di_sum > 0.0 {
114
+ dx.push((plus_di[i] - minus_di[i]).abs() / di_sum * 100.0);
115
+ } else {
116
+ dx.push(0.0);
117
+ }
118
+ }
119
+
120
+ let adx_values = super::ema(&dx, period);
121
+
122
+ ADXResult {
123
+ adx: adx_values,
124
+ plus_di,
125
+ minus_di,
126
+ }
127
+ }
128
+
129
+ /// Ichimoku Cloud
130
+ ///
131
+ /// A comprehensive indicator defining support/resistance,
132
+ /// trend direction, and momentum.
133
+ ///
134
+ /// # Arguments
135
+ /// * `high` - High prices
136
+ /// * `low` - Low prices
137
+ /// * `close` - Close prices
138
+ /// * `tenkan_period` - Tenkan-sen period (default: 9)
139
+ /// * `kijun_period` - Kijun-sen period (default: 26)
140
+ /// * `senkou_b_period` - Senkou Span B period (default: 52)
141
+ ///
142
+ /// # Returns
143
+ /// An IchimokuResult with all five components.
144
+ pub fn ichimoku(
145
+ high: &[f64],
146
+ low: &[f64],
147
+ close: &[f64],
148
+ tenkan_period: usize,
149
+ kijun_period: usize,
150
+ senkou_b_period: usize,
151
+ ) -> IchimokuResult {
152
+ if high.len() < senkou_b_period || tenkan_period == 0 || kijun_period == 0 || senkou_b_period == 0 {
153
+ return IchimokuResult {
154
+ tenkan: Vec::new(),
155
+ kijun: Vec::new(),
156
+ senkou_a: Vec::new(),
157
+ senkou_b: Vec::new(),
158
+ chikou: Vec::new(),
159
+ };
160
+ }
161
+
162
+ let mut tenkan: Vec<f64> = Vec::new();
163
+ let mut kijun: Vec<f64> = Vec::new();
164
+
165
+ // Calculate Tenkan-sen
166
+ for i in (tenkan_period - 1)..close.len() {
167
+ let start = i + 1 - tenkan_period;
168
+ let highest = max(&high[start..=i]);
169
+ let lowest = min(&low[start..=i]);
170
+ tenkan.push((highest + lowest) / 2.0);
171
+ }
172
+
173
+ // Calculate Kijun-sen
174
+ for i in (kijun_period - 1)..close.len() {
175
+ let start = i + 1 - kijun_period;
176
+ let highest = max(&high[start..=i]);
177
+ let lowest = min(&low[start..=i]);
178
+ kijun.push((highest + lowest) / 2.0);
179
+ }
180
+
181
+ // Senkou A (Leading Span A): (Tenkan + Kijun) / 2
182
+ let tenkan_offset = kijun_period - tenkan_period;
183
+ let mut senkou_a: Vec<f64> = Vec::new();
184
+ for i in 0..kijun.len() {
185
+ if i + tenkan_offset < tenkan.len() {
186
+ senkou_a.push((tenkan[i + tenkan_offset] + kijun[i]) / 2.0);
187
+ }
188
+ }
189
+
190
+ // Senkou B (Leading Span B)
191
+ let mut senkou_b: Vec<f64> = Vec::new();
192
+ for i in (senkou_b_period - 1)..close.len() {
193
+ let start = i + 1 - senkou_b_period;
194
+ let highest = max(&high[start..=i]);
195
+ let lowest = min(&low[start..=i]);
196
+ senkou_b.push((highest + lowest) / 2.0);
197
+ }
198
+
199
+ // Chikou (Lagging Span): Close shifted back 26 periods
200
+ let chikou = if close.len() > 26 {
201
+ close[26..].to_vec()
202
+ } else {
203
+ Vec::new()
204
+ };
205
+
206
+ // Align outputs
207
+ let senkou_b_offset = senkou_b_period - kijun_period;
208
+
209
+ IchimokuResult {
210
+ tenkan: tenkan[tenkan_offset..].to_vec(),
211
+ kijun,
212
+ senkou_a,
213
+ senkou_b: if senkou_b_offset < senkou_b.len() {
214
+ senkou_b[senkou_b_offset..].to_vec()
215
+ } else {
216
+ senkou_b
217
+ },
218
+ chikou,
219
+ }
220
+ }
221
+
222
+ /// Parabolic SAR (Stop and Reverse)
223
+ ///
224
+ /// A trend-following indicator that sets trailing price stops
225
+ /// for long or short positions.
226
+ ///
227
+ /// # Arguments
228
+ /// * `high` - High prices
229
+ /// * `low` - Low prices
230
+ /// * `af_start` - Starting acceleration factor (default: 0.02)
231
+ /// * `af_increment` - AF increment (default: 0.02)
232
+ /// * `af_max` - Maximum acceleration factor (default: 0.2)
233
+ ///
234
+ /// # Returns
235
+ /// A SARResult containing SAR values and trend direction.
236
+ pub fn parabolic_sar(
237
+ high: &[f64],
238
+ low: &[f64],
239
+ af_start: f64,
240
+ af_increment: f64,
241
+ af_max: f64,
242
+ ) -> SARResult {
243
+ if high.len() < 2 {
244
+ return SARResult {
245
+ sar: Vec::new(),
246
+ trend: Vec::new(),
247
+ };
248
+ }
249
+
250
+ let mut sar: Vec<f64> = vec![low[0]];
251
+ let mut trend: Vec<bool> = vec![true]; // true = uptrend
252
+ let mut af = af_start;
253
+ let mut ep = high[0];
254
+
255
+ for i in 1..high.len() {
256
+ let prev_sar = sar[i - 1];
257
+ let current_trend = trend[i - 1];
258
+ let mut new_sar = prev_sar + af * (ep - prev_sar);
259
+
260
+ // SAR can't be above prior lows in uptrend
261
+ if current_trend {
262
+ let constraint = if i >= 2 {
263
+ low[i - 1].min(low[i - 2])
264
+ } else {
265
+ low[i - 1]
266
+ };
267
+ new_sar = new_sar.min(constraint);
268
+ } else {
269
+ let constraint = if i >= 2 {
270
+ high[i - 1].max(high[i - 2])
271
+ } else {
272
+ high[i - 1]
273
+ };
274
+ new_sar = new_sar.max(constraint);
275
+ }
276
+
277
+ // Check for trend reversal
278
+ if current_trend && low[i] < new_sar {
279
+ // Uptrend to downtrend
280
+ trend.push(false);
281
+ sar.push(ep);
282
+ af = af_start;
283
+ ep = low[i];
284
+ } else if !current_trend && high[i] > new_sar {
285
+ // Downtrend to uptrend
286
+ trend.push(true);
287
+ sar.push(ep);
288
+ af = af_start;
289
+ ep = high[i];
290
+ } else {
291
+ trend.push(current_trend);
292
+ sar.push(new_sar);
293
+
294
+ // Update extreme point and acceleration factor
295
+ if current_trend && high[i] > ep {
296
+ ep = high[i];
297
+ af = (af + af_increment).min(af_max);
298
+ } else if !current_trend && low[i] < ep {
299
+ ep = low[i];
300
+ af = (af + af_increment).min(af_max);
301
+ }
302
+ }
303
+ }
304
+
305
+ SARResult { sar, trend }
306
+ }
307
+
308
+ #[cfg(test)]
309
+ mod tests {
310
+ use super::*;
311
+
312
+ #[test]
313
+ fn test_adx() {
314
+ let high = vec![10.0, 12.0, 15.0, 14.0, 16.0, 18.0, 17.0, 19.0, 21.0, 20.0,
315
+ 22.0, 24.0, 23.0, 25.0, 27.0, 26.0, 28.0, 30.0, 29.0, 31.0,
316
+ 33.0, 32.0, 34.0, 36.0, 35.0, 37.0, 39.0, 38.0, 40.0, 42.0];
317
+ let low = vec![8.0, 9.0, 11.0, 10.0, 12.0, 14.0, 13.0, 15.0, 17.0, 16.0,
318
+ 18.0, 20.0, 19.0, 21.0, 23.0, 22.0, 24.0, 26.0, 25.0, 27.0,
319
+ 29.0, 28.0, 30.0, 32.0, 31.0, 33.0, 35.0, 34.0, 36.0, 38.0];
320
+ let close = vec![9.0, 11.0, 14.0, 12.0, 15.0, 17.0, 15.0, 18.0, 20.0, 18.0,
321
+ 21.0, 23.0, 21.0, 24.0, 26.0, 24.0, 27.0, 29.0, 27.0, 30.0,
322
+ 32.0, 30.0, 33.0, 35.0, 33.0, 36.0, 38.0, 36.0, 39.0, 41.0];
323
+
324
+ let result = adx(&high, &low, &close, 14);
325
+ assert!(!result.adx.is_empty());
326
+ assert!(!result.plus_di.is_empty());
327
+ assert!(!result.minus_di.is_empty());
328
+ }
329
+
330
+ #[test]
331
+ fn test_ichimoku() {
332
+ // Need enough data for all periods: max(9, 26, 52) = 52 minimum + extra for chikou shift
333
+ let high: Vec<f64> = (1..=80).map(|x| x as f64 * 1.1).collect();
334
+ let low: Vec<f64> = (1..=80).map(|x| x as f64 * 0.9).collect();
335
+ let close: Vec<f64> = (1..=80).map(|x| x as f64).collect();
336
+
337
+ let result = ichimoku(&high, &low, &close, 9, 26, 52);
338
+ assert!(!result.tenkan.is_empty());
339
+ assert!(!result.kijun.is_empty());
340
+ }
341
+
342
+ #[test]
343
+ fn test_parabolic_sar() {
344
+ let high = vec![10.0, 12.0, 15.0, 14.0, 16.0, 18.0, 17.0, 19.0, 21.0, 20.0];
345
+ let low = vec![8.0, 9.0, 11.0, 10.0, 12.0, 14.0, 13.0, 15.0, 17.0, 16.0];
346
+
347
+ let result = parabolic_sar(&high, &low, 0.02, 0.02, 0.2);
348
+ assert_eq!(result.sar.len(), high.len());
349
+ assert_eq!(result.trend.len(), high.len());
350
+ }
351
+ }