use crate::error::Result;
use rust_decimal::Decimal;
use std::time::{Duration, SystemTime};
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct JitPosition {
pub id: Uuid,
pub provider: Uuid,
pub token_id: Uuid,
pub amount_a: Decimal,
pub amount_b: Decimal,
pub added_at: SystemTime,
pub removed_at: Option<SystemTime>,
pub fees_earned: Decimal,
}
#[derive(Debug, Clone)]
pub struct JitOpportunity {
pub token_id: Uuid,
pub pending_trade_volume: Decimal,
pub current_liquidity: Decimal,
pub expected_fee: Decimal,
pub risk_score: Decimal,
pub optimal_amount: Decimal,
pub timing_window: Duration,
pub mev_risk: Decimal,
}
#[derive(Debug)]
pub struct JitLiquidityProvider {
pub min_profit_threshold: Decimal,
pub max_position_size: Decimal,
pub max_mev_risk: Decimal,
pub timing_buffer_ms: u64,
}
impl Default for JitLiquidityProvider {
fn default() -> Self {
Self {
min_profit_threshold: Decimal::new(5, 4), max_position_size: Decimal::new(100000, 0),
max_mev_risk: Decimal::new(3, 1), timing_buffer_ms: 100,
}
}
}
impl JitLiquidityProvider {
pub fn new(
min_profit_threshold: Decimal,
max_position_size: Decimal,
max_mev_risk: Decimal,
timing_buffer_ms: u64,
) -> Self {
Self {
min_profit_threshold,
max_position_size,
max_mev_risk,
timing_buffer_ms,
}
}
pub fn detect_opportunity(
&self,
token_id: Uuid,
pending_trade_volume: Decimal,
current_liquidity: Decimal,
pool_fee_rate: Decimal,
volatility: Decimal,
) -> Result<Option<JitOpportunity>> {
let expected_fee = pending_trade_volume * pool_fee_rate;
let liquidity_ratio = if current_liquidity > Decimal::ZERO {
pending_trade_volume / current_liquidity
} else {
Decimal::MAX
};
let risk_score = (volatility * Decimal::new(5, 1)) + (liquidity_ratio * Decimal::new(3, 1));
let mev_risk = if current_liquidity > Decimal::ZERO {
(pending_trade_volume / current_liquidity).min(Decimal::ONE)
} else {
Decimal::ONE
};
if mev_risk > self.max_mev_risk {
return Ok(None);
}
let optimal_amount = self.calculate_optimal_amount(
pending_trade_volume,
current_liquidity,
expected_fee,
risk_score,
)?;
if expected_fee < self.min_profit_threshold || optimal_amount > self.max_position_size {
return Ok(None);
}
let timing_window = Duration::from_millis(self.timing_buffer_ms);
Ok(Some(JitOpportunity {
token_id,
pending_trade_volume,
current_liquidity,
expected_fee,
risk_score,
optimal_amount,
timing_window,
mev_risk,
}))
}
fn calculate_optimal_amount(
&self,
trade_volume: Decimal,
current_liquidity: Decimal,
_expected_fee: Decimal,
risk_score: Decimal,
) -> Result<Decimal> {
let base_amount = trade_volume * Decimal::new(2, 1);
let liquidity_adjustment = if current_liquidity > Decimal::ZERO {
let ratio = trade_volume / current_liquidity;
if ratio > Decimal::ONE {
Decimal::ONE + ratio
} else {
Decimal::ONE
}
} else {
Decimal::new(2, 0) };
let risk_adjustment = Decimal::ONE - (risk_score * Decimal::new(1, 1));
let risk_adjustment = risk_adjustment.max(Decimal::new(2, 1));
let optimal = base_amount * liquidity_adjustment * risk_adjustment;
Ok(optimal.min(self.max_position_size))
}
pub fn calculate_risk_adjusted_return(&self, opportunity: &JitOpportunity) -> Result<Decimal> {
let expected_return = opportunity.expected_fee;
let mev_adjustment = Decimal::ONE - opportunity.mev_risk;
let risk_adjustment = Decimal::ONE - (opportunity.risk_score * Decimal::new(5, 2));
let adjusted_return = expected_return * mev_adjustment * risk_adjustment.max(Decimal::ZERO);
Ok(adjusted_return)
}
pub fn provide_liquidity(
&self,
provider: Uuid,
opportunity: &JitOpportunity,
) -> Result<JitPosition> {
let amount = opportunity.optimal_amount;
let position = JitPosition {
id: Uuid::new_v4(),
provider,
token_id: opportunity.token_id,
amount_a: amount / Decimal::new(2, 0), amount_b: amount / Decimal::new(2, 0),
added_at: SystemTime::now(),
removed_at: None,
fees_earned: Decimal::ZERO,
};
Ok(position)
}
pub fn remove_liquidity(&self, position: &mut JitPosition, fees_earned: Decimal) -> Result<()> {
position.removed_at = Some(SystemTime::now());
position.fees_earned = fees_earned;
Ok(())
}
pub fn calculate_profitability(&self, position: &JitPosition) -> Result<Decimal> {
if position.removed_at.is_none() {
return Ok(Decimal::ZERO);
}
let capital = position.amount_a + position.amount_b;
let profit = position.fees_earned;
let roi = if capital > Decimal::ZERO {
(profit / capital) * Decimal::new(100, 0) } else {
Decimal::ZERO
};
Ok(roi)
}
}
#[derive(Debug)]
pub struct JitStrategyOptimizer {
pub min_liquidity_gap: Decimal,
pub target_fee_capture: Decimal,
pub max_gas_cost_ratio: Decimal,
}
impl Default for JitStrategyOptimizer {
fn default() -> Self {
Self {
min_liquidity_gap: Decimal::new(5, 1), target_fee_capture: Decimal::new(7, 1), max_gas_cost_ratio: Decimal::new(1, 1), }
}
}
impl JitStrategyOptimizer {
pub fn calculate_optimal_timing(
&self,
opportunity: &JitOpportunity,
block_time_ms: u64,
) -> Result<Duration> {
let execution_blocks = 1;
let mev_buffer_blocks = if opportunity.mev_risk > Decimal::new(2, 1) {
2 } else {
1 };
let total_blocks = execution_blocks + mev_buffer_blocks;
let timing_ms = total_blocks * block_time_ms;
Ok(Duration::from_millis(timing_ms))
}
pub fn evaluate_gas_efficiency(
&self,
expected_fee: Decimal,
estimated_gas_cost: Decimal,
) -> Result<bool> {
if expected_fee <= Decimal::ZERO {
return Ok(false);
}
let gas_ratio = estimated_gas_cost / expected_fee;
Ok(gas_ratio <= self.max_gas_cost_ratio)
}
pub fn calculate_capital_efficiency(
&self,
jit_capital: Decimal,
jit_fees: Decimal,
jit_duration_hours: Decimal,
passive_capital: Decimal,
passive_fees_annual: Decimal,
) -> Result<Decimal> {
let hours_per_year = Decimal::new(8760, 0);
let jit_annual_return = if jit_duration_hours > Decimal::ZERO && jit_capital > Decimal::ZERO
{
(jit_fees / jit_capital) * (hours_per_year / jit_duration_hours)
} else {
Decimal::ZERO
};
let passive_annual_return = if passive_capital > Decimal::ZERO {
passive_fees_annual / passive_capital
} else {
Decimal::ZERO
};
let advantage = if passive_annual_return > Decimal::ZERO {
(jit_annual_return - passive_annual_return) / passive_annual_return
* Decimal::new(100, 0)
} else {
Decimal::ZERO
};
Ok(advantage)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_jit_opportunity_detection() {
let provider = JitLiquidityProvider::default();
let token_id = Uuid::new_v4();
let result = provider
.detect_opportunity(
token_id,
Decimal::new(10000, 0), Decimal::new(50000, 0), Decimal::new(3, 3), Decimal::new(15, 2), )
.unwrap();
assert!(result.is_some());
let opp = result.unwrap();
assert!(opp.expected_fee > Decimal::ZERO);
assert!(opp.optimal_amount > Decimal::ZERO);
}
#[test]
fn test_high_mev_risk_rejection() {
let provider = JitLiquidityProvider::default();
let token_id = Uuid::new_v4();
let result = provider
.detect_opportunity(
token_id,
Decimal::new(100000, 0), Decimal::new(10000, 0), Decimal::new(3, 3), Decimal::new(20, 2), )
.unwrap();
assert!(result.is_none());
}
#[test]
fn test_provide_and_remove_liquidity() {
let provider = JitLiquidityProvider::default();
let token_id = Uuid::new_v4();
let provider_id = Uuid::new_v4();
let opportunity = JitOpportunity {
token_id,
pending_trade_volume: Decimal::new(10000, 0),
current_liquidity: Decimal::new(50000, 0),
expected_fee: Decimal::new(30, 0),
risk_score: Decimal::new(2, 1),
optimal_amount: Decimal::new(5000, 0),
timing_window: Duration::from_millis(100),
mev_risk: Decimal::new(1, 1),
};
let mut position = provider
.provide_liquidity(provider_id, &opportunity)
.unwrap();
assert_eq!(position.removed_at, None);
provider
.remove_liquidity(&mut position, Decimal::new(30, 0))
.unwrap();
assert!(position.removed_at.is_some());
assert_eq!(position.fees_earned, Decimal::new(30, 0));
}
#[test]
fn test_profitability_calculation() {
let provider = JitLiquidityProvider::default();
let position = JitPosition {
id: Uuid::new_v4(),
provider: Uuid::new_v4(),
token_id: Uuid::new_v4(),
amount_a: Decimal::new(2500, 0),
amount_b: Decimal::new(2500, 0),
added_at: SystemTime::now(),
removed_at: Some(SystemTime::now()),
fees_earned: Decimal::new(30, 0),
};
let roi = provider.calculate_profitability(&position).unwrap();
assert_eq!(roi, Decimal::new(60, 2));
}
#[test]
fn test_risk_adjusted_return() {
let provider = JitLiquidityProvider::default();
let opportunity = JitOpportunity {
token_id: Uuid::new_v4(),
pending_trade_volume: Decimal::new(10000, 0),
current_liquidity: Decimal::new(50000, 0),
expected_fee: Decimal::new(30, 0),
risk_score: Decimal::new(2, 1),
optimal_amount: Decimal::new(5000, 0),
timing_window: Duration::from_millis(100),
mev_risk: Decimal::new(1, 1),
};
let adjusted_return = provider
.calculate_risk_adjusted_return(&opportunity)
.unwrap();
assert!(adjusted_return < opportunity.expected_fee);
assert!(adjusted_return > Decimal::ZERO);
}
#[test]
fn test_gas_efficiency_evaluation() {
let optimizer = JitStrategyOptimizer::default();
assert!(
optimizer
.evaluate_gas_efficiency(Decimal::new(100, 0), Decimal::new(5, 0))
.unwrap()
);
assert!(
!optimizer
.evaluate_gas_efficiency(Decimal::new(100, 0), Decimal::new(20, 0))
.unwrap()
);
}
#[test]
fn test_capital_efficiency() {
let optimizer = JitStrategyOptimizer::default();
let advantage = optimizer
.calculate_capital_efficiency(
Decimal::new(5000, 0), Decimal::new(30, 0), Decimal::new(1, 0), Decimal::new(50000, 0), Decimal::new(500, 0), )
.unwrap();
assert!(advantage > Decimal::ZERO);
}
#[test]
fn test_optimal_timing_calculation() {
let optimizer = JitStrategyOptimizer::default();
let opportunity = JitOpportunity {
token_id: Uuid::new_v4(),
pending_trade_volume: Decimal::new(10000, 0),
current_liquidity: Decimal::new(50000, 0),
expected_fee: Decimal::new(30, 0),
risk_score: Decimal::new(2, 1),
optimal_amount: Decimal::new(5000, 0),
timing_window: Duration::from_millis(100),
mev_risk: Decimal::new(3, 1), };
let timing = optimizer
.calculate_optimal_timing(&opportunity, 12000)
.unwrap();
assert!(timing.as_millis() >= 12000);
}
}