1use std::f64;
4
5#[derive(Debug, Clone)]
7pub struct FuzzyConfig {
8 pub default_tolerance: f64,
10 pub price_tolerance: f64,
12 pub volume_tolerance: f64,
14 pub adaptive_tolerance: bool,
16}
17
18impl Default for FuzzyConfig {
19 fn default() -> Self {
20 Self {
21 default_tolerance: 0.02, price_tolerance: 0.02, volume_tolerance: 0.05, adaptive_tolerance: true,
25 }
26 }
27}
28
29#[derive(Debug, Clone)]
31pub struct FuzzyMatcher {
32 config: FuzzyConfig,
33}
34
35impl FuzzyMatcher {
36 pub fn new() -> Self {
38 Self {
39 config: FuzzyConfig::default(),
40 }
41 }
42
43 pub fn with_config(config: FuzzyConfig) -> Self {
45 Self { config }
46 }
47
48 pub fn fuzzy_equal(&self, a: f64, b: f64, tolerance: Option<f64>) -> bool {
50 let tol = tolerance.unwrap_or(self.config.default_tolerance);
51 let diff = (a - b).abs();
52 let avg = (a.abs() + b.abs()) / 2.0;
53
54 if avg == 0.0 {
55 diff == 0.0
56 } else {
57 diff / avg <= tol
58 }
59 }
60
61 pub fn fuzzy_greater(&self, a: f64, b: f64, tolerance: Option<f64>) -> bool {
63 let tol = tolerance.unwrap_or(self.config.default_tolerance);
64 a > b * (1.0 - tol)
65 }
66
67 pub fn fuzzy_less(&self, a: f64, b: f64, tolerance: Option<f64>) -> bool {
69 let tol = tolerance.unwrap_or(self.config.default_tolerance);
70 a < b * (1.0 + tol)
71 }
72
73 pub fn adaptive_tolerance(&self, values: &[f64]) -> f64 {
75 if !self.config.adaptive_tolerance || values.len() < 2 {
76 return self.config.default_tolerance;
77 }
78
79 let mean = values.iter().sum::<f64>() / values.len() as f64;
81 let variance = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / values.len() as f64;
82 let std_dev = variance.sqrt();
83
84 let volatility = if mean != 0.0 {
86 std_dev / mean.abs()
87 } else {
88 0.0
89 };
90
91 let scaled_tolerance = self.config.default_tolerance * (1.0 + volatility).min(3.0);
93
94 scaled_tolerance.min(0.1) }
96
97 pub fn match_pattern(&self, conditions: &[(bool, f64)], threshold: f64) -> bool {
99 if conditions.is_empty() {
100 return false;
101 }
102
103 let total_weight: f64 = conditions.iter().map(|(_, w)| w).sum();
104 let matched_weight: f64 = conditions
105 .iter()
106 .filter(|(matched, _)| *matched)
107 .map(|(_, w)| w)
108 .sum();
109
110 if total_weight == 0.0 {
111 let matched_count = conditions.iter().filter(|(m, _)| *m).count();
113 matched_count as f64 / conditions.len() as f64 >= threshold
114 } else {
115 matched_weight / total_weight >= threshold
116 }
117 }
118
119 pub fn sequence_similarity(&self, seq1: &[f64], seq2: &[f64]) -> f64 {
121 if seq1.is_empty() || seq2.is_empty() || seq1.len() != seq2.len() {
122 return 0.0;
123 }
124
125 let norm1 = Self::normalize_sequence(seq1);
127 let norm2 = Self::normalize_sequence(seq2);
128
129 let n = norm1.len() as f64;
131 let sum_xy: f64 = norm1.iter().zip(&norm2).map(|(x, y)| x * y).sum();
132 let sum_x: f64 = norm1.iter().sum();
133 let sum_y: f64 = norm2.iter().sum();
134 let sum_x2: f64 = norm1.iter().map(|x| x * x).sum();
135 let sum_y2: f64 = norm2.iter().map(|y| y * y).sum();
136
137 let numerator = n * sum_xy - sum_x * sum_y;
138 let denominator = ((n * sum_x2 - sum_x * sum_x) * (n * sum_y2 - sum_y * sum_y)).sqrt();
139
140 if denominator == 0.0 {
141 1.0 } else {
143 (numerator / denominator).abs() }
145 }
146
147 fn normalize_sequence(seq: &[f64]) -> Vec<f64> {
149 let mean = seq.iter().sum::<f64>() / seq.len() as f64;
150 let variance = seq.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / seq.len() as f64;
151 let std_dev = variance.sqrt();
152
153 if std_dev == 0.0 {
154 vec![0.0; seq.len()]
155 } else {
156 seq.iter().map(|v| (v - mean) / std_dev).collect()
157 }
158 }
159}
160
161impl Default for FuzzyMatcher {
162 fn default() -> Self {
163 Self::new()
164 }
165}