clmm_lp_optimization/
constraints.rs1use rust_decimal::Decimal;
7use rust_decimal::prelude::FromPrimitive;
8
9#[derive(Debug, Clone)]
11pub struct PositionConstraints {
12 pub min_range_width: Decimal,
14 pub max_range_width: Decimal,
16 pub min_capital: Decimal,
18 pub max_capital: Decimal,
20 pub max_il_threshold: Decimal,
22 pub min_time_in_range: Decimal,
24 pub max_tx_cost_ratio: Decimal,
26}
27
28impl Default for PositionConstraints {
29 fn default() -> Self {
30 Self {
31 min_range_width: Decimal::from_f64(0.01).unwrap(), max_range_width: Decimal::from_f64(0.50).unwrap(), min_capital: Decimal::from(100), max_capital: Decimal::from(1_000_000), max_il_threshold: Decimal::from_f64(0.10).unwrap(), min_time_in_range: Decimal::from_f64(0.50).unwrap(), max_tx_cost_ratio: Decimal::from_f64(0.01).unwrap(), }
39 }
40}
41
42impl PositionConstraints {
43 #[must_use]
45 pub fn new() -> Self {
46 Self::default()
47 }
48
49 #[must_use]
51 pub fn with_min_range_width(mut self, width: Decimal) -> Self {
52 self.min_range_width = width;
53 self
54 }
55
56 #[must_use]
58 pub fn with_max_range_width(mut self, width: Decimal) -> Self {
59 self.max_range_width = width;
60 self
61 }
62
63 #[must_use]
65 pub fn with_capital_bounds(mut self, min: Decimal, max: Decimal) -> Self {
66 self.min_capital = min;
67 self.max_capital = max;
68 self
69 }
70
71 #[must_use]
73 pub fn with_max_il(mut self, max_il: Decimal) -> Self {
74 self.max_il_threshold = max_il;
75 self
76 }
77
78 #[must_use]
80 pub fn with_min_time_in_range(mut self, min_time: Decimal) -> Self {
81 self.min_time_in_range = min_time;
82 self
83 }
84
85 #[must_use]
87 pub fn is_valid_range_width(&self, width: Decimal) -> bool {
88 width >= self.min_range_width && width <= self.max_range_width
89 }
90
91 #[must_use]
93 pub fn is_valid_capital(&self, capital: Decimal) -> bool {
94 capital >= self.min_capital && capital <= self.max_capital
95 }
96
97 #[must_use]
99 pub fn is_acceptable_il(&self, il: Decimal) -> bool {
100 il.abs() <= self.max_il_threshold
101 }
102
103 #[must_use]
105 pub fn meets_time_in_range(&self, time_in_range: Decimal) -> bool {
106 time_in_range >= self.min_time_in_range
107 }
108}
109
110#[derive(Debug, Clone)]
112pub struct RebalanceConstraints {
113 pub min_rebalance_interval: u64,
115 pub max_rebalance_interval: u64,
117 pub min_price_threshold: Decimal,
119 pub max_price_threshold: Decimal,
121 pub min_il_threshold: Decimal,
123 pub max_il_threshold: Decimal,
125 pub max_rebalances: Option<u32>,
127}
128
129impl Default for RebalanceConstraints {
130 fn default() -> Self {
131 Self {
132 min_rebalance_interval: 1,
133 max_rebalance_interval: 168, min_price_threshold: Decimal::from_f64(0.01).unwrap(), max_price_threshold: Decimal::from_f64(0.20).unwrap(), min_il_threshold: Decimal::from_f64(0.01).unwrap(), max_il_threshold: Decimal::from_f64(0.15).unwrap(), max_rebalances: None,
139 }
140 }
141}
142
143impl RebalanceConstraints {
144 #[must_use]
146 pub fn new() -> Self {
147 Self::default()
148 }
149
150 #[must_use]
152 pub fn with_interval_bounds(mut self, min: u64, max: u64) -> Self {
153 self.min_rebalance_interval = min;
154 self.max_rebalance_interval = max;
155 self
156 }
157
158 #[must_use]
160 pub fn with_price_threshold_bounds(mut self, min: Decimal, max: Decimal) -> Self {
161 self.min_price_threshold = min;
162 self.max_price_threshold = max;
163 self
164 }
165
166 #[must_use]
168 pub fn with_il_threshold_bounds(mut self, min: Decimal, max: Decimal) -> Self {
169 self.min_il_threshold = min;
170 self.max_il_threshold = max;
171 self
172 }
173
174 #[must_use]
176 pub fn with_max_rebalances(mut self, max: u32) -> Self {
177 self.max_rebalances = Some(max);
178 self
179 }
180
181 #[must_use]
183 pub fn is_valid_interval(&self, interval: u64) -> bool {
184 interval >= self.min_rebalance_interval && interval <= self.max_rebalance_interval
185 }
186
187 #[must_use]
189 pub fn is_valid_price_threshold(&self, threshold: Decimal) -> bool {
190 threshold >= self.min_price_threshold && threshold <= self.max_price_threshold
191 }
192
193 #[must_use]
195 pub fn is_valid_il_threshold(&self, threshold: Decimal) -> bool {
196 threshold >= self.min_il_threshold && threshold <= self.max_il_threshold
197 }
198}
199
200#[derive(Debug, Clone, Default)]
202pub struct OptimizationConstraints {
203 pub position: PositionConstraints,
205 pub rebalance: RebalanceConstraints,
207}
208
209impl OptimizationConstraints {
210 #[must_use]
212 pub fn new() -> Self {
213 Self::default()
214 }
215
216 #[must_use]
218 pub fn with_position(mut self, constraints: PositionConstraints) -> Self {
219 self.position = constraints;
220 self
221 }
222
223 #[must_use]
225 pub fn with_rebalance(mut self, constraints: RebalanceConstraints) -> Self {
226 self.rebalance = constraints;
227 self
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn test_position_constraints_default() {
237 let constraints = PositionConstraints::default();
238 assert_eq!(
239 constraints.min_range_width,
240 Decimal::from_f64(0.01).unwrap()
241 );
242 assert_eq!(
243 constraints.max_range_width,
244 Decimal::from_f64(0.50).unwrap()
245 );
246 }
247
248 #[test]
249 fn test_position_constraints_validation() {
250 let constraints = PositionConstraints::default();
251
252 assert!(constraints.is_valid_range_width(Decimal::from_f64(0.10).unwrap()));
253 assert!(!constraints.is_valid_range_width(Decimal::from_f64(0.005).unwrap()));
254 assert!(!constraints.is_valid_range_width(Decimal::from_f64(0.60).unwrap()));
255
256 assert!(constraints.is_valid_capital(Decimal::from(1000)));
257 assert!(!constraints.is_valid_capital(Decimal::from(50)));
258
259 assert!(constraints.is_acceptable_il(Decimal::from_f64(0.05).unwrap()));
260 assert!(!constraints.is_acceptable_il(Decimal::from_f64(0.15).unwrap()));
261 }
262
263 #[test]
264 fn test_rebalance_constraints_validation() {
265 let constraints = RebalanceConstraints::default();
266
267 assert!(constraints.is_valid_interval(24));
268 assert!(!constraints.is_valid_interval(0));
269 assert!(!constraints.is_valid_interval(200));
270
271 assert!(constraints.is_valid_price_threshold(Decimal::from_f64(0.05).unwrap()));
272 assert!(!constraints.is_valid_price_threshold(Decimal::from_f64(0.005).unwrap()));
273 }
274
275 #[test]
276 fn test_constraints_builder() {
277 let constraints = PositionConstraints::new()
278 .with_min_range_width(Decimal::from_f64(0.02).unwrap())
279 .with_max_il(Decimal::from_f64(0.05).unwrap());
280
281 assert_eq!(
282 constraints.min_range_width,
283 Decimal::from_f64(0.02).unwrap()
284 );
285 assert_eq!(
286 constraints.max_il_threshold,
287 Decimal::from_f64(0.05).unwrap()
288 );
289 }
290}