use rust_decimal::Decimal;
use rust_decimal::prelude::FromPrimitive;
#[derive(Debug, Clone)]
pub struct PositionConstraints {
pub min_range_width: Decimal,
pub max_range_width: Decimal,
pub min_capital: Decimal,
pub max_capital: Decimal,
pub max_il_threshold: Decimal,
pub min_time_in_range: Decimal,
pub max_tx_cost_ratio: Decimal,
}
impl Default for PositionConstraints {
fn default() -> Self {
Self {
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(), }
}
}
impl PositionConstraints {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_min_range_width(mut self, width: Decimal) -> Self {
self.min_range_width = width;
self
}
#[must_use]
pub fn with_max_range_width(mut self, width: Decimal) -> Self {
self.max_range_width = width;
self
}
#[must_use]
pub fn with_capital_bounds(mut self, min: Decimal, max: Decimal) -> Self {
self.min_capital = min;
self.max_capital = max;
self
}
#[must_use]
pub fn with_max_il(mut self, max_il: Decimal) -> Self {
self.max_il_threshold = max_il;
self
}
#[must_use]
pub fn with_min_time_in_range(mut self, min_time: Decimal) -> Self {
self.min_time_in_range = min_time;
self
}
#[must_use]
pub fn is_valid_range_width(&self, width: Decimal) -> bool {
width >= self.min_range_width && width <= self.max_range_width
}
#[must_use]
pub fn is_valid_capital(&self, capital: Decimal) -> bool {
capital >= self.min_capital && capital <= self.max_capital
}
#[must_use]
pub fn is_acceptable_il(&self, il: Decimal) -> bool {
il.abs() <= self.max_il_threshold
}
#[must_use]
pub fn meets_time_in_range(&self, time_in_range: Decimal) -> bool {
time_in_range >= self.min_time_in_range
}
}
#[derive(Debug, Clone)]
pub struct RebalanceConstraints {
pub min_rebalance_interval: u64,
pub max_rebalance_interval: u64,
pub min_price_threshold: Decimal,
pub max_price_threshold: Decimal,
pub min_il_threshold: Decimal,
pub max_il_threshold: Decimal,
pub max_rebalances: Option<u32>,
}
impl Default for RebalanceConstraints {
fn default() -> Self {
Self {
min_rebalance_interval: 1,
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,
}
}
}
impl RebalanceConstraints {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_interval_bounds(mut self, min: u64, max: u64) -> Self {
self.min_rebalance_interval = min;
self.max_rebalance_interval = max;
self
}
#[must_use]
pub fn with_price_threshold_bounds(mut self, min: Decimal, max: Decimal) -> Self {
self.min_price_threshold = min;
self.max_price_threshold = max;
self
}
#[must_use]
pub fn with_il_threshold_bounds(mut self, min: Decimal, max: Decimal) -> Self {
self.min_il_threshold = min;
self.max_il_threshold = max;
self
}
#[must_use]
pub fn with_max_rebalances(mut self, max: u32) -> Self {
self.max_rebalances = Some(max);
self
}
#[must_use]
pub fn is_valid_interval(&self, interval: u64) -> bool {
interval >= self.min_rebalance_interval && interval <= self.max_rebalance_interval
}
#[must_use]
pub fn is_valid_price_threshold(&self, threshold: Decimal) -> bool {
threshold >= self.min_price_threshold && threshold <= self.max_price_threshold
}
#[must_use]
pub fn is_valid_il_threshold(&self, threshold: Decimal) -> bool {
threshold >= self.min_il_threshold && threshold <= self.max_il_threshold
}
}
#[derive(Debug, Clone, Default)]
pub struct OptimizationConstraints {
pub position: PositionConstraints,
pub rebalance: RebalanceConstraints,
}
impl OptimizationConstraints {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_position(mut self, constraints: PositionConstraints) -> Self {
self.position = constraints;
self
}
#[must_use]
pub fn with_rebalance(mut self, constraints: RebalanceConstraints) -> Self {
self.rebalance = constraints;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_position_constraints_default() {
let constraints = PositionConstraints::default();
assert_eq!(
constraints.min_range_width,
Decimal::from_f64(0.01).unwrap()
);
assert_eq!(
constraints.max_range_width,
Decimal::from_f64(0.50).unwrap()
);
}
#[test]
fn test_position_constraints_validation() {
let constraints = PositionConstraints::default();
assert!(constraints.is_valid_range_width(Decimal::from_f64(0.10).unwrap()));
assert!(!constraints.is_valid_range_width(Decimal::from_f64(0.005).unwrap()));
assert!(!constraints.is_valid_range_width(Decimal::from_f64(0.60).unwrap()));
assert!(constraints.is_valid_capital(Decimal::from(1000)));
assert!(!constraints.is_valid_capital(Decimal::from(50)));
assert!(constraints.is_acceptable_il(Decimal::from_f64(0.05).unwrap()));
assert!(!constraints.is_acceptable_il(Decimal::from_f64(0.15).unwrap()));
}
#[test]
fn test_rebalance_constraints_validation() {
let constraints = RebalanceConstraints::default();
assert!(constraints.is_valid_interval(24));
assert!(!constraints.is_valid_interval(0));
assert!(!constraints.is_valid_interval(200));
assert!(constraints.is_valid_price_threshold(Decimal::from_f64(0.05).unwrap()));
assert!(!constraints.is_valid_price_threshold(Decimal::from_f64(0.005).unwrap()));
}
#[test]
fn test_constraints_builder() {
let constraints = PositionConstraints::new()
.with_min_range_width(Decimal::from_f64(0.02).unwrap())
.with_max_il(Decimal::from_f64(0.05).unwrap());
assert_eq!(
constraints.min_range_width,
Decimal::from_f64(0.02).unwrap()
);
assert_eq!(
constraints.max_il_threshold,
Decimal::from_f64(0.05).unwrap()
);
}
}