package profitcalc import ( "fmt" "math/big" "github.com/fraktal/mev-beta/internal/logger" ) // SlippageProtector provides slippage calculation and protection for arbitrage trades type SlippageProtector struct { logger *logger.Logger maxSlippageBps int64 // Maximum allowed slippage in basis points liquidityBuffer float64 // Buffer factor for liquidity calculations } // SlippageAnalysis contains detailed slippage analysis for a trade type SlippageAnalysis struct { TradeAmount *big.Float // Amount being traded PoolLiquidity *big.Float // Available liquidity in pool EstimatedSlippage float64 // Estimated slippage percentage SlippageBps int64 // Slippage in basis points PriceImpact float64 // Price impact percentage EffectivePrice *big.Float // Price after slippage MinAmountOut *big.Float // Minimum amount out with slippage protection IsAcceptable bool // Whether slippage is within acceptable limits RiskLevel string // "Low", "Medium", "High", "Extreme" Recommendation string // Trading recommendation } // NewSlippageProtector creates a new slippage protection manager func NewSlippageProtector(logger *logger.Logger) *SlippageProtector { return &SlippageProtector{ logger: logger, maxSlippageBps: 500, // 5% maximum slippage liquidityBuffer: 0.8, // Use 80% of available liquidity for calculations } } // AnalyzeSlippage performs comprehensive slippage analysis for a potential trade func (sp *SlippageProtector) AnalyzeSlippage( tradeAmount *big.Float, poolLiquidity *big.Float, currentPrice *big.Float, ) *SlippageAnalysis { if tradeAmount == nil || poolLiquidity == nil || currentPrice == nil { return &SlippageAnalysis{ IsAcceptable: false, RiskLevel: "Extreme", Recommendation: "Insufficient data for slippage calculation", } } // Calculate trade size as percentage of pool liquidity tradeSizeRatio := new(big.Float).Quo(tradeAmount, poolLiquidity) tradeSizeFloat, _ := tradeSizeRatio.Float64() // Estimate slippage using simplified AMM formula // For constant product AMMs: slippage ≈ trade_size / (2 * liquidity) estimatedSlippage := tradeSizeFloat / 2.0 // Apply curve adjustment for larger trades (non-linear slippage) if tradeSizeFloat > 0.1 { // > 10% of pool // Quadratic increase for large trades estimatedSlippage = estimatedSlippage * (1 + tradeSizeFloat) } slippageBps := int64(estimatedSlippage * 10000) // Calculate price impact (similar to slippage but different calculation) priceImpact := sp.calculatePriceImpact(tradeSizeFloat) // Calculate effective price after slippage slippageFactor := 1.0 - estimatedSlippage effectivePrice := new(big.Float).Mul(currentPrice, big.NewFloat(slippageFactor)) // Calculate minimum amount out with slippage protection minAmountOut := new(big.Float).Quo(tradeAmount, effectivePrice) // Determine risk level and acceptability riskLevel, isAcceptable := sp.assessRiskLevel(slippageBps, tradeSizeFloat) recommendation := sp.generateRecommendation(slippageBps, tradeSizeFloat, riskLevel) analysis := &SlippageAnalysis{ TradeAmount: tradeAmount, PoolLiquidity: poolLiquidity, EstimatedSlippage: estimatedSlippage, SlippageBps: slippageBps, PriceImpact: priceImpact, EffectivePrice: effectivePrice, MinAmountOut: minAmountOut, IsAcceptable: isAcceptable, RiskLevel: riskLevel, Recommendation: recommendation, } sp.logger.Debug(fmt.Sprintf("Slippage analysis: Trade=%s, Liquidity=%s, Slippage=%.2f%%, Risk=%s", tradeAmount.String(), poolLiquidity.String(), estimatedSlippage*100, riskLevel)) return analysis } // calculatePriceImpact calculates price impact using AMM mechanics func (sp *SlippageProtector) calculatePriceImpact(tradeSizeRatio float64) float64 { // For constant product AMMs (like Uniswap V2): // Price impact = trade_size / (1 + trade_size) priceImpact := tradeSizeRatio / (1.0 + tradeSizeRatio) // Cap at 100% if priceImpact > 1.0 { priceImpact = 1.0 } return priceImpact } // assessRiskLevel determines risk level based on slippage and trade size func (sp *SlippageProtector) assessRiskLevel(slippageBps int64, tradeSizeRatio float64) (string, bool) { isAcceptable := slippageBps <= sp.maxSlippageBps var riskLevel string switch { case slippageBps <= 50: // <= 0.5% riskLevel = "Low" case slippageBps <= 200: // <= 2% riskLevel = "Medium" case slippageBps <= 500: // <= 5% riskLevel = "High" default: riskLevel = "Extreme" isAcceptable = false } // Additional checks for trade size if tradeSizeRatio > 0.5 { // > 50% of pool riskLevel = "Extreme" isAcceptable = false } else if tradeSizeRatio > 0.2 { // > 20% of pool if riskLevel == "Low" { riskLevel = "Medium" } else if riskLevel == "Medium" { riskLevel = "High" } } return riskLevel, isAcceptable } // generateRecommendation provides trading recommendations based on analysis func (sp *SlippageProtector) generateRecommendation(slippageBps int64, tradeSizeRatio float64, riskLevel string) string { switch riskLevel { case "Low": return "Safe to execute - low slippage expected" case "Medium": if tradeSizeRatio > 0.1 { return "Consider splitting trade into smaller parts" } return "Proceed with caution - moderate slippage expected" case "High": return "High slippage risk - consider reducing trade size or finding alternative routes" case "Extreme": if tradeSizeRatio > 0.5 { return "Trade too large for pool - split into multiple smaller trades" } return "Excessive slippage - avoid this trade" default: return "Unable to assess - insufficient data" } } // CalculateOptimalTradeSize calculates optimal trade size to stay within slippage limits func (sp *SlippageProtector) CalculateOptimalTradeSize( poolLiquidity *big.Float, maxSlippageBps int64, ) *big.Float { if poolLiquidity == nil || poolLiquidity.Sign() <= 0 { return big.NewFloat(0) } // Convert max slippage to ratio maxSlippageRatio := float64(maxSlippageBps) / 10000.0 // For simplified AMM: optimal_trade_size = 2 * liquidity * max_slippage optimalRatio := 2.0 * maxSlippageRatio // Apply safety factor safetyFactor := 0.8 // Use 80% of optimal to be conservative optimalRatio *= safetyFactor optimalSize := new(big.Float).Mul(poolLiquidity, big.NewFloat(optimalRatio)) sp.logger.Debug(fmt.Sprintf("Calculated optimal trade size: %s (%.2f%% of pool) for max slippage %d bps", optimalSize.String(), optimalRatio*100, maxSlippageBps)) return optimalSize } // EstimateGasForSlippage estimates additional gas needed for slippage protection func (sp *SlippageProtector) EstimateGasForSlippage(analysis *SlippageAnalysis) uint64 { baseGas := uint64(0) // Higher slippage might require more complex routing switch analysis.RiskLevel { case "Low": baseGas = 0 // No additional gas case "Medium": baseGas = 20000 // Additional gas for price checks case "High": baseGas = 50000 // Additional gas for complex routing case "Extreme": baseGas = 100000 // Maximum additional gas for emergency handling } return baseGas } // SetMaxSlippage updates the maximum allowed slippage func (sp *SlippageProtector) SetMaxSlippage(bps int64) { sp.maxSlippageBps = bps sp.logger.Info(fmt.Sprintf("Updated maximum slippage to %d bps (%.2f%%)", bps, float64(bps)/100)) } // GetMaxSlippage returns the current maximum slippage setting func (sp *SlippageProtector) GetMaxSlippage() int64 { return sp.maxSlippageBps } // ValidateTradeParameters performs comprehensive validation of trade parameters func (sp *SlippageProtector) ValidateTradeParameters( tradeAmount *big.Float, poolLiquidity *big.Float, minLiquidity *big.Float, ) error { if tradeAmount == nil || tradeAmount.Sign() <= 0 { return fmt.Errorf("invalid trade amount: must be positive") } if poolLiquidity == nil || poolLiquidity.Sign() <= 0 { return fmt.Errorf("invalid pool liquidity: must be positive") } if minLiquidity != nil && poolLiquidity.Cmp(minLiquidity) < 0 { return fmt.Errorf("insufficient pool liquidity: %s < %s required", poolLiquidity.String(), minLiquidity.String()) } // Check if trade is reasonable relative to pool size tradeSizeRatio := new(big.Float).Quo(tradeAmount, poolLiquidity) tradeSizeFloat, _ := tradeSizeRatio.Float64() if tradeSizeFloat > 0.9 { // > 90% of pool return fmt.Errorf("trade size too large: %.1f%% of pool liquidity", tradeSizeFloat*100) } return nil } // CalculateSlippageAdjustedProfit adjusts profit calculations for slippage func (sp *SlippageProtector) CalculateSlippageAdjustedProfit( grossProfit *big.Float, analysis *SlippageAnalysis, ) *big.Float { if grossProfit == nil || analysis == nil { return big.NewFloat(0) } // Reduce profit by estimated slippage impact slippageImpact := big.NewFloat(analysis.EstimatedSlippage) slippageReduction := new(big.Float).Mul(grossProfit, slippageImpact) adjustedProfit := new(big.Float).Sub(grossProfit, slippageReduction) // Ensure profit doesn't go negative due to slippage if adjustedProfit.Sign() < 0 { adjustedProfit = big.NewFloat(0) } sp.logger.Debug(fmt.Sprintf("Slippage-adjusted profit: %s -> %s (reduction: %s)", grossProfit.String(), adjustedProfit.String(), slippageReduction.String())) return adjustedProfit }