@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,431 @@
1
+ //! Breakout Detection
2
+ //!
3
+ //! Functions for detecting breakouts from support/resistance levels.
4
+
5
+ use serde::{Deserialize, Serialize};
6
+
7
+ use super::sr::SupportResistanceLevel;
8
+
9
+ /// Direction of a breakout
10
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11
+ pub enum BreakoutDirection {
12
+ Up,
13
+ Down,
14
+ }
15
+
16
+ /// A detected breakout
17
+ #[derive(Debug, Clone, Serialize, Deserialize)]
18
+ pub struct Breakout {
19
+ /// The price level that was broken
20
+ pub level: f64,
21
+ /// Direction of the breakout
22
+ pub direction: BreakoutDirection,
23
+ /// Volume ratio compared to average
24
+ pub volume_ratio: f64,
25
+ /// Whether the breakout has been confirmed (follow-through)
26
+ pub confirmed: bool,
27
+ /// Index where breakout occurred
28
+ pub index: usize,
29
+ /// The support/resistance level that was broken
30
+ pub sr_level: Option<SupportResistanceLevel>,
31
+ }
32
+
33
+ impl Breakout {
34
+ /// Create a new breakout
35
+ pub fn new(level: f64, direction: BreakoutDirection, index: usize) -> Self {
36
+ Self {
37
+ level,
38
+ direction,
39
+ volume_ratio: 1.0,
40
+ confirmed: false,
41
+ index,
42
+ sr_level: None,
43
+ }
44
+ }
45
+
46
+ /// Add volume ratio
47
+ pub fn with_volume(mut self, ratio: f64) -> Self {
48
+ self.volume_ratio = ratio;
49
+ self
50
+ }
51
+
52
+ /// Set confirmation status
53
+ pub fn with_confirmation(mut self, confirmed: bool) -> Self {
54
+ self.confirmed = confirmed;
55
+ self
56
+ }
57
+
58
+ /// Attach the original support/resistance level
59
+ pub fn with_sr_level(mut self, level: SupportResistanceLevel) -> Self {
60
+ self.sr_level = Some(level);
61
+ self
62
+ }
63
+ }
64
+
65
+ /// Detect breakouts from support/resistance levels
66
+ ///
67
+ /// # Arguments
68
+ /// * `close` - Array of close prices
69
+ /// * `levels` - Support/resistance levels to check for breakouts
70
+ /// * `volume` - Array of volume data
71
+ /// * `volume_threshold` - Multiple of average volume required for valid breakout
72
+ ///
73
+ /// # Returns
74
+ /// Vector of detected breakouts
75
+ pub fn detect_breakouts(
76
+ close: &[f64],
77
+ levels: &[SupportResistanceLevel],
78
+ volume: &[f64],
79
+ volume_threshold: f64,
80
+ ) -> Vec<Breakout> {
81
+ let mut breakouts = Vec::new();
82
+
83
+ if close.len() < 2 || volume.is_empty() {
84
+ return breakouts;
85
+ }
86
+
87
+ // Calculate average volume
88
+ let avg_volume = volume.iter().sum::<f64>() / volume.len() as f64;
89
+ if avg_volume <= 0.0 {
90
+ return breakouts;
91
+ }
92
+
93
+ for i in 1..close.len() {
94
+ for level in levels {
95
+ let prev_close = close[i - 1];
96
+ let curr_close = close[i];
97
+
98
+ // Bullish breakout (resistance broken)
99
+ if prev_close < level.price && curr_close > level.price {
100
+ let vol_idx = i.min(volume.len() - 1);
101
+ let vol_ratio = volume[vol_idx] / avg_volume;
102
+
103
+ if vol_ratio >= volume_threshold {
104
+ breakouts.push(
105
+ Breakout::new(level.price, BreakoutDirection::Up, i)
106
+ .with_volume(vol_ratio)
107
+ .with_sr_level(level.clone()),
108
+ );
109
+ }
110
+ }
111
+
112
+ // Bearish breakout (support broken)
113
+ if prev_close > level.price && curr_close < level.price {
114
+ let vol_idx = i.min(volume.len() - 1);
115
+ let vol_ratio = volume[vol_idx] / avg_volume;
116
+
117
+ if vol_ratio >= volume_threshold {
118
+ breakouts.push(
119
+ Breakout::new(level.price, BreakoutDirection::Down, i)
120
+ .with_volume(vol_ratio)
121
+ .with_sr_level(level.clone()),
122
+ );
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ // Check for confirmation (follow-through)
129
+ let close_len = close.len();
130
+ for breakout in &mut breakouts {
131
+ let look_ahead = (breakout.index + 3).min(close_len - 1);
132
+ if look_ahead > breakout.index {
133
+ match breakout.direction {
134
+ BreakoutDirection::Up => {
135
+ breakout.confirmed = close[look_ahead] > breakout.level;
136
+ }
137
+ BreakoutDirection::Down => {
138
+ breakout.confirmed = close[look_ahead] < breakout.level;
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ breakouts
145
+ }
146
+
147
+ /// Detect breakouts from simple price levels
148
+ ///
149
+ /// Simplified version that detects breakouts from a list of price levels
150
+ /// without requiring SupportResistanceLevel structs.
151
+ ///
152
+ /// # Arguments
153
+ /// * `close` - Array of close prices
154
+ /// * `levels` - Price levels to check for breakouts
155
+ /// * `volume` - Array of volume data
156
+ /// * `volume_threshold` - Multiple of average volume required for valid breakout
157
+ ///
158
+ /// # Returns
159
+ /// Vector of detected breakouts (without sr_level populated)
160
+ pub fn detect_breakouts_from_levels(
161
+ close: &[f64],
162
+ levels: &[f64],
163
+ volume: &[f64],
164
+ volume_threshold: f64,
165
+ ) -> Vec<Breakout> {
166
+ let mut breakouts = Vec::new();
167
+
168
+ if close.len() < 2 || volume.is_empty() {
169
+ return breakouts;
170
+ }
171
+
172
+ // Calculate average volume
173
+ let avg_volume = volume.iter().sum::<f64>() / volume.len() as f64;
174
+ if avg_volume <= 0.0 {
175
+ return breakouts;
176
+ }
177
+
178
+ for i in 1..close.len() {
179
+ for &level in levels {
180
+ let prev_close = close[i - 1];
181
+ let curr_close = close[i];
182
+
183
+ // Bullish breakout (resistance broken)
184
+ if prev_close < level && curr_close > level {
185
+ let vol_idx = i.min(volume.len() - 1);
186
+ let vol_ratio = volume[vol_idx] / avg_volume;
187
+
188
+ if vol_ratio >= volume_threshold {
189
+ breakouts.push(
190
+ Breakout::new(level, BreakoutDirection::Up, i).with_volume(vol_ratio),
191
+ );
192
+ }
193
+ }
194
+
195
+ // Bearish breakout (support broken)
196
+ if prev_close > level && curr_close < level {
197
+ let vol_idx = i.min(volume.len() - 1);
198
+ let vol_ratio = volume[vol_idx] / avg_volume;
199
+
200
+ if vol_ratio >= volume_threshold {
201
+ breakouts.push(
202
+ Breakout::new(level, BreakoutDirection::Down, i).with_volume(vol_ratio),
203
+ );
204
+ }
205
+ }
206
+ }
207
+ }
208
+
209
+ // Check for confirmation (follow-through)
210
+ let close_len = close.len();
211
+ for breakout in &mut breakouts {
212
+ let look_ahead = (breakout.index + 3).min(close_len - 1);
213
+ if look_ahead > breakout.index {
214
+ match breakout.direction {
215
+ BreakoutDirection::Up => {
216
+ breakout.confirmed = close[look_ahead] > breakout.level;
217
+ }
218
+ BreakoutDirection::Down => {
219
+ breakout.confirmed = close[look_ahead] < breakout.level;
220
+ }
221
+ }
222
+ }
223
+ }
224
+
225
+ breakouts
226
+ }
227
+
228
+ /// Detect all-time high breakouts
229
+ ///
230
+ /// Detects when price breaks above all previous highs.
231
+ ///
232
+ /// # Arguments
233
+ /// * `high` - Array of high prices
234
+ /// * `close` - Array of close prices
235
+ ///
236
+ /// # Returns
237
+ /// Vector of detected breakouts (direction always Up)
238
+ pub fn detect_all_time_high_breakouts(high: &[f64], close: &[f64]) -> Vec<Breakout> {
239
+ let mut breakouts = Vec::new();
240
+
241
+ if high.len() < 2 || close.len() < 2 {
242
+ return breakouts;
243
+ }
244
+
245
+ let mut running_high = high[0];
246
+
247
+ for i in 1..high.len() {
248
+ if high[i] > running_high {
249
+ // New all-time high
250
+ breakouts.push(Breakout::new(running_high, BreakoutDirection::Up, i));
251
+ running_high = high[i];
252
+ }
253
+ }
254
+
255
+ breakouts
256
+ }
257
+
258
+ /// Detect all-time low breakouts (breakdown)
259
+ ///
260
+ /// Detects when price breaks below all previous lows.
261
+ ///
262
+ /// # Arguments
263
+ /// * `low` - Array of low prices
264
+ /// * `close` - Array of close prices
265
+ ///
266
+ /// # Returns
267
+ /// Vector of detected breakouts (direction always Down)
268
+ pub fn detect_all_time_low_breakouts(low: &[f64], close: &[f64]) -> Vec<Breakout> {
269
+ let mut breakouts = Vec::new();
270
+
271
+ if low.len() < 2 || close.len() < 2 {
272
+ return breakouts;
273
+ }
274
+
275
+ let mut running_low = low[0];
276
+
277
+ for i in 1..low.len() {
278
+ if low[i] < running_low {
279
+ // New all-time low
280
+ breakouts.push(Breakout::new(running_low, BreakoutDirection::Down, i));
281
+ running_low = low[i];
282
+ }
283
+ }
284
+
285
+ breakouts
286
+ }
287
+
288
+ /// Detect consolidation breakouts
289
+ ///
290
+ /// Detects breakouts from a period of low volatility (consolidation).
291
+ ///
292
+ /// # Arguments
293
+ /// * `high` - Array of high prices
294
+ /// * `low` - Array of low prices
295
+ /// * `close` - Array of close prices
296
+ /// * `consolidation_periods` - Minimum number of periods for consolidation
297
+ /// * `range_threshold` - Maximum range (as % of price) to consider consolidation
298
+ ///
299
+ /// # Returns
300
+ /// Vector of detected breakouts
301
+ pub fn detect_consolidation_breakouts(
302
+ high: &[f64],
303
+ low: &[f64],
304
+ close: &[f64],
305
+ consolidation_periods: usize,
306
+ range_threshold: f64,
307
+ ) -> Vec<Breakout> {
308
+ let mut breakouts = Vec::new();
309
+
310
+ if high.len() < consolidation_periods + 1 {
311
+ return breakouts;
312
+ }
313
+
314
+ for i in consolidation_periods..high.len() {
315
+ // Check if the previous periods were consolidating
316
+ let consolidation_start = i - consolidation_periods;
317
+ let window_high = &high[consolidation_start..i];
318
+ let window_low = &low[consolidation_start..i];
319
+
320
+ let highest = window_high.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
321
+ let lowest = window_low.iter().cloned().fold(f64::INFINITY, f64::min);
322
+
323
+ let mid_price = (highest + lowest) / 2.0;
324
+ let range_pct = (highest - lowest) / mid_price;
325
+
326
+ // Check if we were in consolidation
327
+ if range_pct < range_threshold {
328
+ // Check for breakout above consolidation
329
+ if close[i] > highest {
330
+ breakouts.push(
331
+ Breakout::new(highest, BreakoutDirection::Up, i).with_confirmation(true),
332
+ );
333
+ }
334
+ // Check for breakdown below consolidation
335
+ else if close[i] < lowest {
336
+ breakouts.push(
337
+ Breakout::new(lowest, BreakoutDirection::Down, i).with_confirmation(true),
338
+ );
339
+ }
340
+ }
341
+ }
342
+
343
+ breakouts
344
+ }
345
+
346
+ #[cfg(test)]
347
+ mod tests {
348
+ use super::*;
349
+ use super::super::sr::LevelType;
350
+
351
+ fn create_test_sr_level(price: f64, level_type: LevelType) -> SupportResistanceLevel {
352
+ SupportResistanceLevel {
353
+ price,
354
+ strength: 1,
355
+ touches: 1,
356
+ first_touch: 0,
357
+ last_touch: 0,
358
+ level_type,
359
+ }
360
+ }
361
+
362
+ #[test]
363
+ fn test_detect_breakouts_basic() {
364
+ let close = vec![95.0, 98.0, 99.0, 102.0, 105.0]; // Crosses above 100 at index 3
365
+ let volume = vec![100.0, 110.0, 120.0, 150.0, 140.0];
366
+ let levels = vec![create_test_sr_level(100.0, LevelType::Resistance)];
367
+
368
+ let breakouts = detect_breakouts(&close, &levels, &volume, 1.0);
369
+
370
+ assert!(!breakouts.is_empty());
371
+ assert_eq!(breakouts[0].direction, BreakoutDirection::Up);
372
+ }
373
+
374
+ #[test]
375
+ fn test_detect_breakouts_from_levels() {
376
+ let close = vec![95.0, 98.0, 99.0, 102.0, 105.0]; // Crosses above 100 at index 3
377
+ let volume = vec![100.0, 110.0, 120.0, 150.0, 140.0];
378
+ let levels = vec![100.0, 105.0];
379
+
380
+ let breakouts = detect_breakouts_from_levels(&close, &levels, &volume, 1.0);
381
+
382
+ assert!(!breakouts.is_empty());
383
+ }
384
+
385
+ #[test]
386
+ fn test_detect_all_time_high_breakouts() {
387
+ let high = vec![100.0, 102.0, 101.0, 105.0, 103.0, 108.0];
388
+ let close = vec![99.0, 101.0, 100.0, 104.0, 102.0, 107.0];
389
+
390
+ let breakouts = detect_all_time_high_breakouts(&high, &close);
391
+
392
+ assert!(!breakouts.is_empty());
393
+ // Should detect breakouts at indices where new highs are made
394
+ }
395
+
396
+ #[test]
397
+ fn test_detect_consolidation_breakouts() {
398
+ // Consolidation then breakout
399
+ let high = vec![
400
+ 100.0, 101.0, 100.5, 101.0, // Consolidation (range ~1%)
401
+ 105.0, // Breakout
402
+ ];
403
+ let low = vec![
404
+ 99.0, 99.5, 99.5, 99.8,
405
+ 102.0,
406
+ ];
407
+ let close = vec![
408
+ 99.5, 100.5, 100.0, 100.5,
409
+ 104.0, // Closes above consolidation
410
+ ];
411
+
412
+ let breakouts = detect_consolidation_breakouts(&high, &low, &close, 4, 0.03);
413
+
414
+ assert!(!breakouts.is_empty());
415
+ assert_eq!(breakouts[0].direction, BreakoutDirection::Up);
416
+ }
417
+
418
+ #[test]
419
+ fn test_breakout_confirmation() {
420
+ let close = vec![95.0, 100.0, 102.0, 101.0, 103.0]; // Stays above 100
421
+ let volume = vec![100.0, 150.0, 120.0, 110.0, 130.0];
422
+ let levels = vec![create_test_sr_level(100.0, LevelType::Resistance)];
423
+
424
+ let breakouts = detect_breakouts(&close, &levels, &volume, 1.0);
425
+
426
+ // Breakout should be confirmed since price stays above level
427
+ if !breakouts.is_empty() {
428
+ assert!(breakouts[0].confirmed);
429
+ }
430
+ }
431
+ }