@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,394 @@
|
|
|
1
|
+
//! Pattern Recognition Module
|
|
2
|
+
//!
|
|
3
|
+
//! Provides candlestick and chart pattern recognition, support/resistance detection,
|
|
4
|
+
//! trend line analysis, and breakout detection.
|
|
5
|
+
//!
|
|
6
|
+
//! ## Sub-modules
|
|
7
|
+
//! - `sr`: Support and resistance level detection, trend line finding
|
|
8
|
+
//! - `chart`: Chart pattern detection (double tops/bottoms, head and shoulders, triangles, etc.)
|
|
9
|
+
//! - `breakout`: Breakout detection from levels and consolidations
|
|
10
|
+
//!
|
|
11
|
+
//! ## Usage
|
|
12
|
+
//! ```ignore
|
|
13
|
+
//! use quant_rust::patterns::{
|
|
14
|
+
//! find_support_resistance, detect_all_patterns, detect_breakouts,
|
|
15
|
+
//! SupportResistanceLevel, PatternMatch, Breakout
|
|
16
|
+
//! };
|
|
17
|
+
//! ```
|
|
18
|
+
|
|
19
|
+
pub mod breakout;
|
|
20
|
+
pub mod chart;
|
|
21
|
+
pub mod sr;
|
|
22
|
+
|
|
23
|
+
// Re-export types from sub-modules
|
|
24
|
+
pub use breakout::{Breakout, BreakoutDirection};
|
|
25
|
+
pub use chart::{
|
|
26
|
+
detect_all_patterns, detect_channels, detect_double_bottom, detect_double_top,
|
|
27
|
+
detect_gaps, detect_head_and_shoulders, detect_inverse_head_and_shoulders, detect_triangles,
|
|
28
|
+
PatternDirection, PatternMatch, PatternType,
|
|
29
|
+
};
|
|
30
|
+
pub use sr::{find_support_resistance, find_trend_lines, LevelType, SupportResistanceLevel, TrendLine};
|
|
31
|
+
|
|
32
|
+
// Re-export candlestick pattern types for backward compatibility
|
|
33
|
+
use crate::types::{CandlePattern, OHLCV, PatternResult};
|
|
34
|
+
use serde::{Deserialize, Serialize};
|
|
35
|
+
|
|
36
|
+
// Re-export breakout functions
|
|
37
|
+
pub use breakout::{
|
|
38
|
+
detect_all_time_high_breakouts, detect_all_time_low_breakouts,
|
|
39
|
+
detect_breakouts_from_levels, detect_consolidation_breakouts, detect_breakouts,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/// Pattern recognition configuration
|
|
43
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
44
|
+
pub struct PatternConfig {
|
|
45
|
+
/// Doji threshold (body / range ratio)
|
|
46
|
+
pub doji_threshold: f64,
|
|
47
|
+
/// Minimum pattern confidence to report
|
|
48
|
+
pub min_confidence: f64,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
impl Default for PatternConfig {
|
|
52
|
+
fn default() -> Self {
|
|
53
|
+
Self {
|
|
54
|
+
doji_threshold: 0.1,
|
|
55
|
+
min_confidence: 0.5,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Detect candlestick patterns in OHLCV data
|
|
61
|
+
///
|
|
62
|
+
/// This is a convenience function for detecting candlestick patterns.
|
|
63
|
+
/// For chart patterns, use the functions from the `chart` module.
|
|
64
|
+
pub fn detect_patterns(data: &[OHLCV], config: &PatternConfig) -> Vec<PatternResult> {
|
|
65
|
+
if data.len() < 3 {
|
|
66
|
+
return vec![];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let mut results = Vec::new();
|
|
70
|
+
|
|
71
|
+
for i in 2..data.len() {
|
|
72
|
+
// Check single-candle patterns
|
|
73
|
+
if let Some(pattern) = detect_doji(&data[i], config) {
|
|
74
|
+
results.push(pattern);
|
|
75
|
+
}
|
|
76
|
+
if let Some(pattern) = detect_hammer(&data[i], config) {
|
|
77
|
+
results.push(pattern);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check two-candle patterns
|
|
81
|
+
if i >= 1 {
|
|
82
|
+
if let Some(pattern) = detect_engulfing(&data[i - 1], &data[i], config) {
|
|
83
|
+
results.push(pattern);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check three-candle patterns
|
|
88
|
+
if let Some(pattern) = detect_morning_star(&data[i - 2], &data[i - 1], &data[i], config) {
|
|
89
|
+
results.push(pattern);
|
|
90
|
+
}
|
|
91
|
+
if let Some(pattern) = detect_evening_star(&data[i - 2], &data[i - 1], &data[i], config) {
|
|
92
|
+
results.push(pattern);
|
|
93
|
+
}
|
|
94
|
+
if let Some(pattern) = detect_three_soldiers(&data[i - 2], &data[i - 1], &data[i], config)
|
|
95
|
+
{
|
|
96
|
+
results.push(pattern);
|
|
97
|
+
}
|
|
98
|
+
if let Some(pattern) = detect_three_crows(&data[i - 2], &data[i - 1], &data[i], config) {
|
|
99
|
+
results.push(pattern);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Filter by confidence
|
|
104
|
+
results
|
|
105
|
+
.into_iter()
|
|
106
|
+
.filter(|r| r.confidence >= config.min_confidence)
|
|
107
|
+
.collect()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// Detect Doji pattern
|
|
111
|
+
fn detect_doji(candle: &OHLCV, config: &PatternConfig) -> Option<PatternResult> {
|
|
112
|
+
let range = candle.range();
|
|
113
|
+
if range == 0.0 {
|
|
114
|
+
return None;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let body_ratio = candle.body_size() / range;
|
|
118
|
+
|
|
119
|
+
if body_ratio < config.doji_threshold {
|
|
120
|
+
Some(PatternResult {
|
|
121
|
+
pattern: CandlePattern::Doji,
|
|
122
|
+
confidence: 1.0 - body_ratio,
|
|
123
|
+
index: 0, // Will be set by caller
|
|
124
|
+
is_bullish: false, // Doji is neutral
|
|
125
|
+
})
|
|
126
|
+
} else {
|
|
127
|
+
None
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Detect Hammer pattern
|
|
132
|
+
fn detect_hammer(candle: &OHLCV, _config: &PatternConfig) -> Option<PatternResult> {
|
|
133
|
+
let range = candle.range();
|
|
134
|
+
if range == 0.0 {
|
|
135
|
+
return None;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let body = candle.body_size();
|
|
139
|
+
let lower_wick = (candle.close.min(candle.open)) - candle.low;
|
|
140
|
+
let upper_wick = candle.high - (candle.close.max(candle.open));
|
|
141
|
+
|
|
142
|
+
// Hammer: long lower wick (2x body), small upper wick, small body
|
|
143
|
+
if lower_wick >= body * 2.0 && upper_wick < body * 0.5 {
|
|
144
|
+
Some(PatternResult {
|
|
145
|
+
pattern: CandlePattern::Hammer,
|
|
146
|
+
confidence: (lower_wick / body / 3.0).min(1.0),
|
|
147
|
+
index: 0,
|
|
148
|
+
is_bullish: true,
|
|
149
|
+
})
|
|
150
|
+
} else if upper_wick >= body * 2.0 && lower_wick < body * 0.5 {
|
|
151
|
+
Some(PatternResult {
|
|
152
|
+
pattern: CandlePattern::InvertedHammer,
|
|
153
|
+
confidence: (upper_wick / body / 3.0).min(1.0),
|
|
154
|
+
index: 0,
|
|
155
|
+
is_bullish: true,
|
|
156
|
+
})
|
|
157
|
+
} else {
|
|
158
|
+
None
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// Detect Engulfing pattern
|
|
163
|
+
fn detect_engulfing(prev: &OHLCV, curr: &OHLCV, _config: &PatternConfig) -> Option<PatternResult> {
|
|
164
|
+
let prev_body = prev.body_size();
|
|
165
|
+
let curr_body = curr.body_size();
|
|
166
|
+
|
|
167
|
+
// Bullish engulfing: prev bearish, curr bullish, curr engulfs prev
|
|
168
|
+
if prev.is_bearish() && curr.is_bullish() {
|
|
169
|
+
if curr_body > prev_body && curr.open < prev.close && curr.close > prev.open {
|
|
170
|
+
return Some(PatternResult {
|
|
171
|
+
pattern: CandlePattern::BullishEngulfing,
|
|
172
|
+
confidence: (curr_body / prev_body / 2.0).min(1.0),
|
|
173
|
+
index: 0,
|
|
174
|
+
is_bullish: true,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Bearish engulfing: prev bullish, curr bearish, curr engulfs prev
|
|
180
|
+
if prev.is_bullish() && curr.is_bearish() {
|
|
181
|
+
if curr_body > prev_body && curr.open > prev.close && curr.close < prev.open {
|
|
182
|
+
return Some(PatternResult {
|
|
183
|
+
pattern: CandlePattern::BearishEngulfing,
|
|
184
|
+
confidence: (curr_body / prev_body / 2.0).min(1.0),
|
|
185
|
+
index: 0,
|
|
186
|
+
is_bullish: false,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
None
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// Detect Morning Star pattern (bullish reversal)
|
|
195
|
+
fn detect_morning_star(
|
|
196
|
+
first: &OHLCV,
|
|
197
|
+
second: &OHLCV,
|
|
198
|
+
third: &OHLCV,
|
|
199
|
+
_config: &PatternConfig,
|
|
200
|
+
) -> Option<PatternResult> {
|
|
201
|
+
// First: large bearish
|
|
202
|
+
// Second: small body (gap down preferred)
|
|
203
|
+
// Third: large bullish closing above midpoint of first
|
|
204
|
+
|
|
205
|
+
if !first.is_bearish() || !third.is_bullish() {
|
|
206
|
+
return None;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let first_body = first.body_size();
|
|
210
|
+
let second_body = second.body_size();
|
|
211
|
+
let third_body = third.body_size();
|
|
212
|
+
|
|
213
|
+
// Second should be small
|
|
214
|
+
if second_body > first_body * 0.3 {
|
|
215
|
+
return None;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Third should close above midpoint of first
|
|
219
|
+
let first_mid = (first.open + first.close) / 2.0;
|
|
220
|
+
if third.close < first_mid {
|
|
221
|
+
return None;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Third should be large
|
|
225
|
+
if third_body < first_body * 0.6 {
|
|
226
|
+
return None;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
Some(PatternResult {
|
|
230
|
+
pattern: CandlePattern::MorningStar,
|
|
231
|
+
confidence: (third_body / first_body).min(1.0),
|
|
232
|
+
index: 0,
|
|
233
|
+
is_bullish: true,
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/// Detect Evening Star pattern (bearish reversal)
|
|
238
|
+
fn detect_evening_star(
|
|
239
|
+
first: &OHLCV,
|
|
240
|
+
second: &OHLCV,
|
|
241
|
+
third: &OHLCV,
|
|
242
|
+
_config: &PatternConfig,
|
|
243
|
+
) -> Option<PatternResult> {
|
|
244
|
+
// First: large bullish
|
|
245
|
+
// Second: small body (gap up preferred)
|
|
246
|
+
// Third: large bearish closing below midpoint of first
|
|
247
|
+
|
|
248
|
+
if !first.is_bullish() || !third.is_bearish() {
|
|
249
|
+
return None;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let first_body = first.body_size();
|
|
253
|
+
let second_body = second.body_size();
|
|
254
|
+
let third_body = third.body_size();
|
|
255
|
+
|
|
256
|
+
// Second should be small
|
|
257
|
+
if second_body > first_body * 0.3 {
|
|
258
|
+
return None;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Third should close below midpoint of first
|
|
262
|
+
let first_mid = (first.open + first.close) / 2.0;
|
|
263
|
+
if third.close > first_mid {
|
|
264
|
+
return None;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Third should be large
|
|
268
|
+
if third_body < first_body * 0.6 {
|
|
269
|
+
return None;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
Some(PatternResult {
|
|
273
|
+
pattern: CandlePattern::EveningStar,
|
|
274
|
+
confidence: (third_body / first_body).min(1.0),
|
|
275
|
+
index: 0,
|
|
276
|
+
is_bullish: false,
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/// Detect Three White Soldiers pattern
|
|
281
|
+
fn detect_three_soldiers(
|
|
282
|
+
first: &OHLCV,
|
|
283
|
+
second: &OHLCV,
|
|
284
|
+
third: &OHLCV,
|
|
285
|
+
_config: &PatternConfig,
|
|
286
|
+
) -> Option<PatternResult> {
|
|
287
|
+
// Three consecutive bullish candles
|
|
288
|
+
// Each opens within previous body
|
|
289
|
+
// Each closes higher
|
|
290
|
+
|
|
291
|
+
if !first.is_bullish() || !second.is_bullish() || !third.is_bullish() {
|
|
292
|
+
return None;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Check progression
|
|
296
|
+
if second.close <= first.close || third.close <= second.close {
|
|
297
|
+
return None;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Check opens are within previous bodies
|
|
301
|
+
if second.open < first.close.min(first.open)
|
|
302
|
+
|| second.open > first.close.max(first.open)
|
|
303
|
+
|| third.open < second.close.min(second.open)
|
|
304
|
+
|| third.open > second.close.max(second.open)
|
|
305
|
+
{
|
|
306
|
+
return None;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
Some(PatternResult {
|
|
310
|
+
pattern: CandlePattern::ThreeWhiteSoldiers,
|
|
311
|
+
confidence: 0.9,
|
|
312
|
+
index: 0,
|
|
313
|
+
is_bullish: true,
|
|
314
|
+
})
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/// Detect Three Black Crows pattern
|
|
318
|
+
fn detect_three_crows(
|
|
319
|
+
first: &OHLCV,
|
|
320
|
+
second: &OHLCV,
|
|
321
|
+
third: &OHLCV,
|
|
322
|
+
_config: &PatternConfig,
|
|
323
|
+
) -> Option<PatternResult> {
|
|
324
|
+
// Three consecutive bearish candles
|
|
325
|
+
// Each opens within previous body
|
|
326
|
+
// Each closes lower
|
|
327
|
+
|
|
328
|
+
if !first.is_bearish() || !second.is_bearish() || !third.is_bearish() {
|
|
329
|
+
return None;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Check progression
|
|
333
|
+
if second.close >= first.close || third.close >= second.close {
|
|
334
|
+
return None;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Check opens are within previous bodies
|
|
338
|
+
if second.open < first.close.min(first.open)
|
|
339
|
+
|| second.open > first.close.max(first.open)
|
|
340
|
+
|| third.open < second.close.min(second.open)
|
|
341
|
+
|| third.open > second.close.max(second.open)
|
|
342
|
+
{
|
|
343
|
+
return None;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
Some(PatternResult {
|
|
347
|
+
pattern: CandlePattern::ThreeBlackCrows,
|
|
348
|
+
confidence: 0.9,
|
|
349
|
+
index: 0,
|
|
350
|
+
is_bullish: false,
|
|
351
|
+
})
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
#[cfg(test)]
|
|
355
|
+
mod tests {
|
|
356
|
+
use super::*;
|
|
357
|
+
|
|
358
|
+
#[test]
|
|
359
|
+
fn test_doji_detection() {
|
|
360
|
+
let candle = OHLCV::new(0, 100.0, 105.0, 95.0, 100.5, 1000.0);
|
|
361
|
+
let config = PatternConfig::default();
|
|
362
|
+
let result = detect_doji(&candle, &config);
|
|
363
|
+
assert!(result.is_some());
|
|
364
|
+
assert_eq!(result.unwrap().pattern, CandlePattern::Doji);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
#[test]
|
|
368
|
+
fn test_bullish_engulfing() {
|
|
369
|
+
let prev = OHLCV::new(0, 102.0, 103.0, 98.0, 99.0, 1000.0);
|
|
370
|
+
let curr = OHLCV::new(0, 98.5, 105.0, 98.0, 104.0, 1000.0);
|
|
371
|
+
let config = PatternConfig::default();
|
|
372
|
+
let result = detect_engulfing(&prev, &curr, &config);
|
|
373
|
+
assert!(result.is_some());
|
|
374
|
+
assert_eq!(result.unwrap().pattern, CandlePattern::BullishEngulfing);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
#[test]
|
|
378
|
+
fn test_module_reexports() {
|
|
379
|
+
// Test that all types are properly re-exported
|
|
380
|
+
use super::*;
|
|
381
|
+
|
|
382
|
+
// Support/Resistance types
|
|
383
|
+
let _level_type = LevelType::Support;
|
|
384
|
+
|
|
385
|
+
// Chart pattern types
|
|
386
|
+
let _pattern_type = PatternType::DoubleTop;
|
|
387
|
+
let _direction = PatternDirection::Bullish;
|
|
388
|
+
|
|
389
|
+
// Breakout types
|
|
390
|
+
let _breakout_dir = BreakoutDirection::Up;
|
|
391
|
+
|
|
392
|
+
// This test just verifies the types compile correctly
|
|
393
|
+
}
|
|
394
|
+
}
|