@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.
- package/README.md +161 -0
- package/bun-ffi.d.ts +54 -0
- package/dist/index.js +576 -0
- package/dist/src/index.d.ts +324 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +403 -0
- package/dist/types/index.d.ts.map +1 -0
- package/native/README.md +62 -0
- package/native/darwin-arm64/libquant_rust.dylib +0 -0
- package/package.json +70 -0
- package/scripts/postinstall.cjs +85 -0
- package/src/ffi.rs +496 -0
- package/src/index.ts +1073 -0
- package/src/indicators/ma.rs +222 -0
- package/src/indicators/mod.rs +18 -0
- package/src/indicators/momentum.rs +353 -0
- package/src/indicators/sr.rs +195 -0
- package/src/indicators/trend.rs +351 -0
- package/src/indicators/volatility.rs +270 -0
- package/src/indicators/volume.rs +213 -0
- package/src/lib.rs +130 -0
- package/src/patterns/breakout.rs +431 -0
- package/src/patterns/chart.rs +772 -0
- package/src/patterns/mod.rs +394 -0
- package/src/patterns/sr.rs +423 -0
- package/src/prediction/amm.rs +338 -0
- package/src/prediction/arbitrage.rs +230 -0
- package/src/prediction/calibration.rs +317 -0
- package/src/prediction/kelly.rs +232 -0
- package/src/prediction/lmsr.rs +194 -0
- package/src/prediction/mod.rs +59 -0
- package/src/prediction/odds.rs +229 -0
- package/src/prediction/pnl.rs +254 -0
- package/src/prediction/risk.rs +228 -0
- package/src/risk/beta.rs +257 -0
- package/src/risk/drawdown.rs +256 -0
- package/src/risk/leverage.rs +201 -0
- package/src/risk/mod.rs +388 -0
- package/src/risk/portfolio.rs +287 -0
- package/src/risk/ratios.rs +290 -0
- package/src/risk/sizing.rs +194 -0
- package/src/risk/var.rs +222 -0
- package/src/stats/cdf.rs +257 -0
- package/src/stats/correlation.rs +225 -0
- package/src/stats/distribution.rs +194 -0
- package/src/stats/hypothesis.rs +177 -0
- package/src/stats/matrix.rs +346 -0
- package/src/stats/mod.rs +257 -0
- package/src/stats/regression.rs +239 -0
- package/src/stats/rolling.rs +193 -0
- package/src/stats/timeseries.rs +263 -0
- package/src/types.rs +224 -0
- package/src/utils/mod.rs +215 -0
- package/src/utils/normalize.rs +192 -0
- package/src/utils/price.rs +167 -0
- package/src/utils/quantiles.rs +177 -0
- package/src/utils/returns.rs +158 -0
- package/src/utils/rolling.rs +97 -0
- package/src/utils/stats.rs +154 -0
- 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
|
+
}
|