@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,270 @@
|
|
|
1
|
+
//! Volatility Indicators
|
|
2
|
+
//!
|
|
3
|
+
//! Indicators that measure the rate and magnitude of price changes.
|
|
4
|
+
|
|
5
|
+
use crate::utils::{mean, rolling};
|
|
6
|
+
|
|
7
|
+
/// Average True Range (ATR) result
|
|
8
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
9
|
+
pub struct ATRResult {
|
|
10
|
+
/// ATR values (smoothed TR)
|
|
11
|
+
pub atr: Vec<f64>,
|
|
12
|
+
/// True Range values
|
|
13
|
+
pub tr: Vec<f64>,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// Bollinger Bands result
|
|
17
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
18
|
+
pub struct BollingerBandsResult {
|
|
19
|
+
/// Upper band (middle + std_dev * multiplier)
|
|
20
|
+
pub upper: Vec<f64>,
|
|
21
|
+
/// Middle band (SMA)
|
|
22
|
+
pub middle: Vec<f64>,
|
|
23
|
+
/// Lower band (middle - std_dev * multiplier)
|
|
24
|
+
pub lower: Vec<f64>,
|
|
25
|
+
/// Bandwidth ((upper - lower) / middle * 100)
|
|
26
|
+
pub bandwidth: Vec<f64>,
|
|
27
|
+
/// %B (position within bands: (price - lower) / (upper - lower))
|
|
28
|
+
pub percent_b: Vec<f64>,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Keltner Channels result
|
|
32
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
33
|
+
pub struct KeltnerChannelsResult {
|
|
34
|
+
/// Upper channel
|
|
35
|
+
pub upper: Vec<f64>,
|
|
36
|
+
/// Middle channel (EMA)
|
|
37
|
+
pub middle: Vec<f64>,
|
|
38
|
+
/// Lower channel
|
|
39
|
+
pub lower: Vec<f64>,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Average True Range (ATR)
|
|
43
|
+
///
|
|
44
|
+
/// Measures market volatility by decomposing the entire range
|
|
45
|
+
/// of an asset price for a period.
|
|
46
|
+
///
|
|
47
|
+
/// True Range = max(H-L, |H-PrevC|, |L-PrevC|)
|
|
48
|
+
/// ATR = EMA of True Range
|
|
49
|
+
///
|
|
50
|
+
/// # Arguments
|
|
51
|
+
/// * `high` - High prices
|
|
52
|
+
/// * `low` - Low prices
|
|
53
|
+
/// * `close` - Close prices
|
|
54
|
+
/// * `period` - ATR period (default: 14)
|
|
55
|
+
///
|
|
56
|
+
/// # Returns
|
|
57
|
+
/// An ATRResult containing ATR and True Range values.
|
|
58
|
+
pub fn atr(high: &[f64], low: &[f64], close: &[f64], period: usize) -> ATRResult {
|
|
59
|
+
if high.len() < 2 || period == 0 {
|
|
60
|
+
return ATRResult {
|
|
61
|
+
atr: Vec::new(),
|
|
62
|
+
tr: Vec::new(),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Calculate True Range
|
|
67
|
+
let mut tr: Vec<f64> = vec![high[0] - low[0]]; // First TR is just H-L
|
|
68
|
+
|
|
69
|
+
for i in 1..high.len() {
|
|
70
|
+
let hl = high[i] - low[i];
|
|
71
|
+
let hpc = (high[i] - close[i - 1]).abs();
|
|
72
|
+
let lpc = (low[i] - close[i - 1]).abs();
|
|
73
|
+
tr.push(hl.max(hpc).max(lpc));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Calculate ATR using EMA
|
|
77
|
+
let atr_values = super::ema(&tr, period);
|
|
78
|
+
|
|
79
|
+
ATRResult { atr: atr_values, tr }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// Bollinger Bands
|
|
83
|
+
///
|
|
84
|
+
/// A volatility indicator consisting of a middle band (SMA)
|
|
85
|
+
/// and two outer bands (standard deviations away).
|
|
86
|
+
///
|
|
87
|
+
/// # Arguments
|
|
88
|
+
/// * `data` - Price data slice
|
|
89
|
+
/// * `period` - SMA period (default: 20)
|
|
90
|
+
/// * `std_dev_mult` - Standard deviation multiplier (default: 2.0)
|
|
91
|
+
///
|
|
92
|
+
/// # Returns
|
|
93
|
+
/// A BollingerBandsResult containing upper, middle, lower bands, bandwidth, and %B.
|
|
94
|
+
pub fn bollinger_bands(data: &[f64], period: usize, std_dev_mult: f64) -> BollingerBandsResult {
|
|
95
|
+
if data.len() < period || period == 0 {
|
|
96
|
+
return BollingerBandsResult {
|
|
97
|
+
upper: Vec::new(),
|
|
98
|
+
middle: Vec::new(),
|
|
99
|
+
lower: Vec::new(),
|
|
100
|
+
bandwidth: Vec::new(),
|
|
101
|
+
percent_b: Vec::new(),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let middle = super::sma(data, period);
|
|
106
|
+
|
|
107
|
+
// Calculate rolling standard deviation
|
|
108
|
+
let std_devs: Vec<f64> = rolling(data, period, |slice| {
|
|
109
|
+
let avg = mean(slice);
|
|
110
|
+
let variance: f64 = slice.iter().map(|v| (v - avg).powi(2)).sum::<f64>()
|
|
111
|
+
/ (slice.len() - 1) as f64;
|
|
112
|
+
variance.sqrt()
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
let mut upper: Vec<f64> = Vec::new();
|
|
116
|
+
let mut lower: Vec<f64> = Vec::new();
|
|
117
|
+
let mut bandwidth: Vec<f64> = Vec::new();
|
|
118
|
+
let mut percent_b: Vec<f64> = Vec::new();
|
|
119
|
+
|
|
120
|
+
for i in 0..middle.len() {
|
|
121
|
+
let mid = middle[i];
|
|
122
|
+
let sd = std_devs[i];
|
|
123
|
+
let up = mid + std_dev_mult * sd;
|
|
124
|
+
let low = mid - std_dev_mult * sd;
|
|
125
|
+
|
|
126
|
+
upper.push(up);
|
|
127
|
+
lower.push(low);
|
|
128
|
+
bandwidth.push(if mid > 0.0 { ((up - low) / mid) * 100.0 } else { 0.0 });
|
|
129
|
+
|
|
130
|
+
// Percent B: where is price relative to bands
|
|
131
|
+
let price_index = data.len() - middle.len() + i;
|
|
132
|
+
if (up - low).abs() > 1e-10 {
|
|
133
|
+
percent_b.push((data[price_index] - low) / (up - low));
|
|
134
|
+
} else {
|
|
135
|
+
percent_b.push(0.5);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
BollingerBandsResult {
|
|
140
|
+
upper,
|
|
141
|
+
middle,
|
|
142
|
+
lower,
|
|
143
|
+
bandwidth,
|
|
144
|
+
percent_b,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Keltner Channels
|
|
149
|
+
///
|
|
150
|
+
/// A volatility-based envelope using ATR above and below an EMA.
|
|
151
|
+
///
|
|
152
|
+
/// # Arguments
|
|
153
|
+
/// * `high` - High prices
|
|
154
|
+
/// * `low` - Low prices
|
|
155
|
+
/// * `close` - Close prices
|
|
156
|
+
/// * `ema_period` - EMA period (default: 20)
|
|
157
|
+
/// * `atr_period` - ATR period (default: 10)
|
|
158
|
+
/// * `mult` - ATR multiplier (default: 2.0)
|
|
159
|
+
///
|
|
160
|
+
/// # Returns
|
|
161
|
+
/// A KeltnerChannelsResult containing upper, middle, and lower channels.
|
|
162
|
+
pub fn keltner_channels(
|
|
163
|
+
high: &[f64],
|
|
164
|
+
low: &[f64],
|
|
165
|
+
close: &[f64],
|
|
166
|
+
ema_period: usize,
|
|
167
|
+
atr_period: usize,
|
|
168
|
+
mult: f64,
|
|
169
|
+
) -> KeltnerChannelsResult {
|
|
170
|
+
if close.len() < ema_period.max(atr_period) || ema_period == 0 || atr_period == 0 {
|
|
171
|
+
return KeltnerChannelsResult {
|
|
172
|
+
upper: Vec::new(),
|
|
173
|
+
middle: Vec::new(),
|
|
174
|
+
lower: Vec::new(),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let middle = super::ema(close, ema_period);
|
|
179
|
+
let atr_result = atr(high, low, close, atr_period);
|
|
180
|
+
let atr_values = atr_result.atr;
|
|
181
|
+
|
|
182
|
+
// Align ATR with EMA
|
|
183
|
+
let atr_offset = close.len() - atr_values.len();
|
|
184
|
+
let ema_offset = close.len() - middle.len();
|
|
185
|
+
|
|
186
|
+
let upper: Vec<f64> = middle
|
|
187
|
+
.iter()
|
|
188
|
+
.enumerate()
|
|
189
|
+
.filter_map(|(i, &mid)| {
|
|
190
|
+
let atr_idx = i + (ema_offset - atr_offset);
|
|
191
|
+
if atr_idx < atr_values.len() {
|
|
192
|
+
Some(mid + mult * atr_values[atr_idx])
|
|
193
|
+
} else {
|
|
194
|
+
None
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
.collect();
|
|
198
|
+
|
|
199
|
+
let lower: Vec<f64> = middle
|
|
200
|
+
.iter()
|
|
201
|
+
.enumerate()
|
|
202
|
+
.filter_map(|(i, &mid)| {
|
|
203
|
+
let atr_idx = i + (ema_offset - atr_offset);
|
|
204
|
+
if atr_idx < atr_values.len() {
|
|
205
|
+
Some(mid - mult * atr_values[atr_idx])
|
|
206
|
+
} else {
|
|
207
|
+
None
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
.collect();
|
|
211
|
+
|
|
212
|
+
// Ensure all three arrays have the same length
|
|
213
|
+
let min_len = upper.len().min(lower.len());
|
|
214
|
+
|
|
215
|
+
KeltnerChannelsResult {
|
|
216
|
+
upper: upper[..min_len].to_vec(),
|
|
217
|
+
middle: middle[..min_len].to_vec(),
|
|
218
|
+
lower: lower[..min_len].to_vec(),
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#[cfg(test)]
|
|
223
|
+
mod tests {
|
|
224
|
+
use super::*;
|
|
225
|
+
|
|
226
|
+
#[test]
|
|
227
|
+
fn test_atr() {
|
|
228
|
+
let high = vec![10.0, 12.0, 14.0, 16.0, 18.0];
|
|
229
|
+
let low = vec![8.0, 10.0, 11.0, 13.0, 15.0];
|
|
230
|
+
let close = vec![9.0, 11.0, 13.0, 15.0, 17.0];
|
|
231
|
+
|
|
232
|
+
let result = atr(&high, &low, &close, 3);
|
|
233
|
+
assert!(!result.tr.is_empty());
|
|
234
|
+
assert!(!result.atr.is_empty());
|
|
235
|
+
|
|
236
|
+
// First TR should be high[0] - low[0]
|
|
237
|
+
assert!((result.tr[0] - 2.0).abs() < 1e-10);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
#[test]
|
|
241
|
+
fn test_bollinger_bands() {
|
|
242
|
+
let prices: Vec<f64> = (1..=30).map(|x| x as f64).collect();
|
|
243
|
+
let result = bollinger_bands(&prices, 20, 2.0);
|
|
244
|
+
|
|
245
|
+
assert!(!result.upper.is_empty());
|
|
246
|
+
assert!(!result.middle.is_empty());
|
|
247
|
+
assert!(!result.lower.is_empty());
|
|
248
|
+
|
|
249
|
+
// Upper should be above middle, lower should be below
|
|
250
|
+
for i in 0..result.middle.len() {
|
|
251
|
+
assert!(result.upper[i] > result.middle[i]);
|
|
252
|
+
assert!(result.lower[i] < result.middle[i]);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#[test]
|
|
257
|
+
fn test_keltner_channels() {
|
|
258
|
+
let high = vec![10.0, 12.0, 15.0, 14.0, 16.0, 18.0, 17.0, 19.0, 21.0, 20.0,
|
|
259
|
+
22.0, 24.0, 23.0, 25.0, 27.0, 26.0, 28.0, 30.0, 29.0, 31.0];
|
|
260
|
+
let low = vec![8.0, 9.0, 11.0, 10.0, 12.0, 14.0, 13.0, 15.0, 17.0, 16.0,
|
|
261
|
+
18.0, 20.0, 19.0, 21.0, 23.0, 22.0, 24.0, 26.0, 25.0, 27.0];
|
|
262
|
+
let close = vec![9.0, 11.0, 14.0, 12.0, 15.0, 17.0, 15.0, 18.0, 20.0, 18.0,
|
|
263
|
+
21.0, 23.0, 21.0, 24.0, 26.0, 24.0, 27.0, 29.0, 27.0, 30.0];
|
|
264
|
+
|
|
265
|
+
let result = keltner_channels(&high, &low, &close, 10, 10, 2.0);
|
|
266
|
+
assert!(!result.upper.is_empty());
|
|
267
|
+
assert!(!result.middle.is_empty());
|
|
268
|
+
assert!(!result.lower.is_empty());
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
//! Volume Indicators
|
|
2
|
+
//!
|
|
3
|
+
//! Indicators based on trading volume analysis.
|
|
4
|
+
|
|
5
|
+
use crate::utils::{max, min};
|
|
6
|
+
|
|
7
|
+
/// On-Balance Volume (OBV)
|
|
8
|
+
///
|
|
9
|
+
/// A cumulative indicator that adds volume on up days and subtracts
|
|
10
|
+
/// volume on down days, used to detect divergences with price.
|
|
11
|
+
///
|
|
12
|
+
/// # Arguments
|
|
13
|
+
/// * `close` - Close prices
|
|
14
|
+
/// * `volume` - Volume data
|
|
15
|
+
///
|
|
16
|
+
/// # Returns
|
|
17
|
+
/// A vector of OBV values.
|
|
18
|
+
pub fn obv(close: &[f64], volume: &[f64]) -> Vec<f64> {
|
|
19
|
+
if close.len() < 2 || volume.len() < 2 {
|
|
20
|
+
return Vec::new();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let mut result: Vec<f64> = vec![0.0];
|
|
24
|
+
|
|
25
|
+
for i in 1..close.len().min(volume.len()) {
|
|
26
|
+
if close[i] > close[i - 1] {
|
|
27
|
+
result.push(result[i - 1] + volume[i]);
|
|
28
|
+
} else if close[i] < close[i - 1] {
|
|
29
|
+
result.push(result[i - 1] - volume[i]);
|
|
30
|
+
} else {
|
|
31
|
+
result.push(result[i - 1]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
result
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Money Flow Index (MFI)
|
|
39
|
+
///
|
|
40
|
+
/// A momentum indicator using price and volume to identify
|
|
41
|
+
/// overbought/oversold conditions. Also known as Volume RSI.
|
|
42
|
+
///
|
|
43
|
+
/// # Arguments
|
|
44
|
+
/// * `high` - High prices
|
|
45
|
+
/// * `low` - Low prices
|
|
46
|
+
/// * `close` - Close prices
|
|
47
|
+
/// * `volume` - Volume data
|
|
48
|
+
/// * `period` - MFI period (default: 14)
|
|
49
|
+
///
|
|
50
|
+
/// # Returns
|
|
51
|
+
/// A vector of MFI values (0-100).
|
|
52
|
+
pub fn mfi(
|
|
53
|
+
high: &[f64],
|
|
54
|
+
low: &[f64],
|
|
55
|
+
close: &[f64],
|
|
56
|
+
volume: &[f64],
|
|
57
|
+
period: usize,
|
|
58
|
+
) -> Vec<f64> {
|
|
59
|
+
if close.len() < period + 1 || period == 0 {
|
|
60
|
+
return Vec::new();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Calculate typical prices and money flows
|
|
64
|
+
let typical_prices: Vec<f64> = close
|
|
65
|
+
.iter()
|
|
66
|
+
.enumerate()
|
|
67
|
+
.map(|(i, &c)| (high[i] + low[i] + c) / 3.0)
|
|
68
|
+
.collect();
|
|
69
|
+
|
|
70
|
+
let money_flows: Vec<f64> = typical_prices
|
|
71
|
+
.iter()
|
|
72
|
+
.enumerate()
|
|
73
|
+
.map(|(i, &tp)| tp * volume[i])
|
|
74
|
+
.collect();
|
|
75
|
+
|
|
76
|
+
let mut result: Vec<f64> = Vec::new();
|
|
77
|
+
|
|
78
|
+
for i in period..close.len() {
|
|
79
|
+
let mut positive_flow = 0.0;
|
|
80
|
+
let mut negative_flow = 0.0;
|
|
81
|
+
|
|
82
|
+
for j in (i + 1 - period)..=i {
|
|
83
|
+
if typical_prices[j] > typical_prices[j - 1] {
|
|
84
|
+
positive_flow += money_flows[j];
|
|
85
|
+
} else {
|
|
86
|
+
negative_flow += money_flows[j];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if negative_flow == 0.0 {
|
|
91
|
+
result.push(100.0);
|
|
92
|
+
} else {
|
|
93
|
+
let mf_ratio = positive_flow / negative_flow;
|
|
94
|
+
result.push(100.0 - 100.0 / (1.0 + mf_ratio));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
result
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Volume Profile
|
|
102
|
+
///
|
|
103
|
+
/// Analyzes trading activity at specific price levels to identify
|
|
104
|
+
/// areas of high volume (support/resistance zones).
|
|
105
|
+
///
|
|
106
|
+
/// # Arguments
|
|
107
|
+
/// * `high` - High prices
|
|
108
|
+
/// * `low` - Low prices
|
|
109
|
+
/// * `close` - Close prices
|
|
110
|
+
/// * `volume` - Volume data
|
|
111
|
+
/// * `levels` - Number of price levels to analyze
|
|
112
|
+
///
|
|
113
|
+
/// # Returns
|
|
114
|
+
/// A vector of (price_level, volume) tuples.
|
|
115
|
+
pub fn volume_profile(
|
|
116
|
+
high: &[f64],
|
|
117
|
+
low: &[f64],
|
|
118
|
+
close: &[f64],
|
|
119
|
+
volume: &[f64],
|
|
120
|
+
levels: usize,
|
|
121
|
+
) -> Vec<(f64, f64)> {
|
|
122
|
+
if high.is_empty() || low.is_empty() || levels == 0 {
|
|
123
|
+
return Vec::new();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let max_high = max(high);
|
|
127
|
+
let min_low = min(low);
|
|
128
|
+
|
|
129
|
+
if max_high.is_nan() || min_low.is_nan() {
|
|
130
|
+
return Vec::new();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let price_range = max_high - min_low;
|
|
134
|
+
let level_size = price_range / levels as f64;
|
|
135
|
+
|
|
136
|
+
// Initialize price levels with zero volume
|
|
137
|
+
let mut profile: Vec<(f64, f64)> = (0..=levels)
|
|
138
|
+
.map(|i| (min_low + i as f64 * level_size, 0.0))
|
|
139
|
+
.collect();
|
|
140
|
+
|
|
141
|
+
// Distribute volume across price levels
|
|
142
|
+
for i in 0..close.len().min(high.len()).min(low.len()).min(volume.len()) {
|
|
143
|
+
let h = high[i];
|
|
144
|
+
let l = low[i];
|
|
145
|
+
let v = volume[i];
|
|
146
|
+
let range = h - l;
|
|
147
|
+
|
|
148
|
+
if range > 0.0 {
|
|
149
|
+
// Distribute volume proportionally across levels within the candle's range
|
|
150
|
+
for (level, vol) in profile.iter_mut() {
|
|
151
|
+
if *level >= l && *level <= h {
|
|
152
|
+
*vol += v / levels as f64;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
profile
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#[cfg(test)]
|
|
162
|
+
mod tests {
|
|
163
|
+
use super::*;
|
|
164
|
+
|
|
165
|
+
#[test]
|
|
166
|
+
fn test_obv() {
|
|
167
|
+
let close = vec![10.0, 11.0, 10.5, 12.0, 11.5];
|
|
168
|
+
let volume = vec![100.0, 150.0, 120.0, 200.0, 180.0];
|
|
169
|
+
|
|
170
|
+
let result = obv(&close, &volume);
|
|
171
|
+
|
|
172
|
+
assert_eq!(result.len(), 5);
|
|
173
|
+
assert_eq!(result[0], 0.0); // Starting point
|
|
174
|
+
assert_eq!(result[1], 150.0); // Up day: +150
|
|
175
|
+
assert_eq!(result[2], 30.0); // Down day: 150 - 120
|
|
176
|
+
assert_eq!(result[3], 230.0); // Up day: 30 + 200
|
|
177
|
+
assert_eq!(result[4], 50.0); // Down day: 230 - 180
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#[test]
|
|
181
|
+
fn test_mfi() {
|
|
182
|
+
let high = vec![10.0, 12.0, 14.0, 16.0, 18.0, 17.0, 19.0, 21.0, 20.0, 22.0,
|
|
183
|
+
24.0, 23.0, 25.0, 27.0, 26.0, 28.0, 30.0, 29.0, 31.0, 33.0];
|
|
184
|
+
let low = vec![8.0, 9.0, 11.0, 13.0, 15.0, 14.0, 16.0, 18.0, 17.0, 19.0,
|
|
185
|
+
21.0, 20.0, 22.0, 24.0, 23.0, 25.0, 27.0, 26.0, 28.0, 30.0];
|
|
186
|
+
let close = vec![9.0, 11.0, 13.0, 15.0, 17.0, 15.0, 18.0, 20.0, 18.0, 21.0,
|
|
187
|
+
23.0, 21.0, 24.0, 26.0, 24.0, 27.0, 29.0, 27.0, 30.0, 32.0];
|
|
188
|
+
let volume = vec![100.0, 150.0, 120.0, 200.0, 180.0, 160.0, 220.0, 190.0, 170.0, 240.0,
|
|
189
|
+
210.0, 180.0, 260.0, 230.0, 200.0, 280.0, 250.0, 220.0, 300.0, 270.0];
|
|
190
|
+
|
|
191
|
+
let result = mfi(&high, &low, &close, &volume, 14);
|
|
192
|
+
|
|
193
|
+
assert!(!result.is_empty());
|
|
194
|
+
// MFI should be between 0 and 100
|
|
195
|
+
for &val in &result {
|
|
196
|
+
assert!(val >= 0.0 && val <= 100.0);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
#[test]
|
|
201
|
+
fn test_volume_profile() {
|
|
202
|
+
let high = vec![10.0, 12.0, 14.0];
|
|
203
|
+
let low = vec![8.0, 10.0, 12.0];
|
|
204
|
+
let close = vec![9.0, 11.0, 13.0];
|
|
205
|
+
let volume = vec![100.0, 150.0, 120.0];
|
|
206
|
+
|
|
207
|
+
let result = volume_profile(&high, &low, &close, &volume, 5);
|
|
208
|
+
|
|
209
|
+
assert!(!result.is_empty());
|
|
210
|
+
// Should have 6 levels (0 to 5 inclusive)
|
|
211
|
+
assert_eq!(result.len(), 6);
|
|
212
|
+
}
|
|
213
|
+
}
|
package/src/lib.rs
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
//! # Quant Rust - High-Performance Quantitative Finance Library
|
|
2
|
+
//!
|
|
3
|
+
//! A Rust library providing quantitative finance computations with FFI bindings for Bun/TypeScript.
|
|
4
|
+
//!
|
|
5
|
+
//! ## Modules
|
|
6
|
+
//! - `prediction`: Prediction market calculations (AMM, LMSR, Kelly criterion)
|
|
7
|
+
//! - `indicators`: Technical indicators (RSI, MACD, Bollinger Bands, etc.)
|
|
8
|
+
//! - `risk`: Risk management (VaR, CVaR, Sharpe ratio, etc.)
|
|
9
|
+
//! - `stats`: Statistical analysis (correlation, regression, distributions)
|
|
10
|
+
//! - `patterns`: Pattern recognition (candlestick, chart patterns, support/resistance)
|
|
11
|
+
//! - `utils`: Utility functions and helpers
|
|
12
|
+
|
|
13
|
+
pub mod types;
|
|
14
|
+
pub mod ffi;
|
|
15
|
+
pub mod utils;
|
|
16
|
+
|
|
17
|
+
#[cfg(feature = "prediction")]
|
|
18
|
+
pub mod prediction;
|
|
19
|
+
|
|
20
|
+
#[cfg(feature = "indicators")]
|
|
21
|
+
pub mod indicators;
|
|
22
|
+
|
|
23
|
+
#[cfg(feature = "risk")]
|
|
24
|
+
pub mod risk;
|
|
25
|
+
|
|
26
|
+
#[cfg(feature = "stats")]
|
|
27
|
+
pub mod stats;
|
|
28
|
+
|
|
29
|
+
#[cfg(feature = "patterns")]
|
|
30
|
+
pub mod patterns;
|
|
31
|
+
|
|
32
|
+
// Re-export types first (these are the primary definitions)
|
|
33
|
+
pub use types::{OHLCV, Signal, IndicatorResult, CandlePattern, PatternResult};
|
|
34
|
+
|
|
35
|
+
// Re-export utils
|
|
36
|
+
pub use utils::{sum, mean, median, variance, std_dev, min, max, rolling, round_to, clamp};
|
|
37
|
+
|
|
38
|
+
#[cfg(feature = "prediction")]
|
|
39
|
+
pub use prediction::{
|
|
40
|
+
Outcome,
|
|
41
|
+
odds::*,
|
|
42
|
+
kelly::KellyResult,
|
|
43
|
+
amm::AMMState,
|
|
44
|
+
lmsr::LMSRState,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
#[cfg(feature = "indicators")]
|
|
48
|
+
pub use indicators::*;
|
|
49
|
+
|
|
50
|
+
#[cfg(feature = "risk")]
|
|
51
|
+
pub use risk::{VaRResult, DrawdownResult, SharpeResult, calculate_var, calculate_drawdown, calculate_sharpe_ratio, calculate_sortino_ratio, calculate_beta_alpha};
|
|
52
|
+
|
|
53
|
+
#[cfg(feature = "stats")]
|
|
54
|
+
pub use stats::{
|
|
55
|
+
// Types
|
|
56
|
+
RegressionResult,
|
|
57
|
+
MultipleRegressionResult,
|
|
58
|
+
DistributionStats,
|
|
59
|
+
Moments,
|
|
60
|
+
CorrelationResult,
|
|
61
|
+
RollingStats,
|
|
62
|
+
RollingRegressionResult,
|
|
63
|
+
TTestResult,
|
|
64
|
+
ADFResult,
|
|
65
|
+
AutocorrelationResult,
|
|
66
|
+
DoubleExponentialResult,
|
|
67
|
+
TripleExponentialResult,
|
|
68
|
+
// Correlation functions
|
|
69
|
+
correlation,
|
|
70
|
+
covariance,
|
|
71
|
+
pearson_correlation,
|
|
72
|
+
spearman_correlation,
|
|
73
|
+
correlation_with_p_value,
|
|
74
|
+
correlation_matrix,
|
|
75
|
+
// Regression functions
|
|
76
|
+
linear_regression,
|
|
77
|
+
multiple_regression,
|
|
78
|
+
// Distribution functions
|
|
79
|
+
distribution_stats,
|
|
80
|
+
moments,
|
|
81
|
+
distribution_statistics,
|
|
82
|
+
// Rolling statistics
|
|
83
|
+
rolling_stats,
|
|
84
|
+
rolling_correlation,
|
|
85
|
+
rolling_regression,
|
|
86
|
+
rolling_z_score,
|
|
87
|
+
// Hypothesis testing
|
|
88
|
+
one_sample_t_test,
|
|
89
|
+
two_sample_t_test,
|
|
90
|
+
adf_test,
|
|
91
|
+
// Distribution functions (CDF)
|
|
92
|
+
normal_cdf,
|
|
93
|
+
t_cdf,
|
|
94
|
+
gamma,
|
|
95
|
+
beta,
|
|
96
|
+
incomplete_beta,
|
|
97
|
+
// Time series
|
|
98
|
+
autocorrelation,
|
|
99
|
+
partial_autocorrelation,
|
|
100
|
+
exponential_smoothing,
|
|
101
|
+
double_exponential_smoothing,
|
|
102
|
+
triple_exponential_smoothing,
|
|
103
|
+
// Matrix operations
|
|
104
|
+
transpose,
|
|
105
|
+
matrix_multiply,
|
|
106
|
+
matrix_vector_multiply,
|
|
107
|
+
matrix_inverse,
|
|
108
|
+
determinant,
|
|
109
|
+
solve_linear_system,
|
|
110
|
+
percentile,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
#[cfg(feature = "patterns")]
|
|
114
|
+
pub use patterns::{
|
|
115
|
+
// Config and candlestick detection
|
|
116
|
+
PatternConfig, detect_patterns,
|
|
117
|
+
// Support/Resistance
|
|
118
|
+
find_support_resistance, find_trend_lines,
|
|
119
|
+
SupportResistanceLevel, LevelType, TrendLine,
|
|
120
|
+
// Chart patterns
|
|
121
|
+
detect_all_patterns, detect_double_top, detect_double_bottom,
|
|
122
|
+
detect_head_and_shoulders, detect_inverse_head_and_shoulders,
|
|
123
|
+
detect_triangles, detect_gaps, detect_channels,
|
|
124
|
+
PatternMatch, PatternType, PatternDirection,
|
|
125
|
+
// Breakouts
|
|
126
|
+
detect_breakouts, detect_breakouts_from_levels,
|
|
127
|
+
detect_all_time_high_breakouts, detect_all_time_low_breakouts,
|
|
128
|
+
detect_consolidation_breakouts,
|
|
129
|
+
Breakout, BreakoutDirection,
|
|
130
|
+
};
|