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() // CRITICAL FIX: Use proper constant product AMM formula // Bug: Old formula "tradeSize / 2.0" is mathematically incorrect // Correct Uniswap V2 formula: dy = (y * dx * 997) / (x * 1000 + dx * 997) // Slippage = (marketPrice - executionPrice) / marketPrice estimatedSlippage := sp.calculateConstantProductSlippage(tradeAmount, poolLiquidity, currentPrice) 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 } // calculateConstantProductSlippage calculates slippage using correct Uniswap V2 constant product formula // Formula: dy = (y * dx * 997) / (x * 1000 + dx * 997) // Where: x = reserveIn, y = reserveOut, dx = amountIn, dy = amountOut // Slippage = (marketPrice - executionPrice) / marketPrice func (sp *SlippageProtector) calculateConstantProductSlippage( amountIn *big.Float, reserveIn *big.Float, marketPrice *big.Float, ) float64 { // Handle edge cases if amountIn == nil || reserveIn == nil || marketPrice == nil { return 1.0 // 100% slippage for invalid inputs } if amountIn.Sign() <= 0 || reserveIn.Sign() <= 0 || marketPrice.Sign() <= 0 { return 1.0 } // Calculate: dx * 997 (Uniswap V2 fee is 0.3%, so 997/1000 remains) fee := big.NewFloat(997) dx997 := new(big.Float).Mul(amountIn, fee) // Calculate: x * 1000 x1000 := new(big.Float).Mul(reserveIn, big.NewFloat(1000)) // Calculate: x * 1000 + dx * 997 denominator := new(big.Float).Add(x1000, dx997) // Calculate execution price = dx / (x * 1000 + dx * 997) // This represents how much the price moved executionRatio := new(big.Float).Quo(dx997, denominator) // For small trades, slippage ≈ executionRatio / 2 // For larger trades, use more precise calculation tradeSizeRatio := new(big.Float).Quo(amountIn, reserveIn) tradeSizeFloat, _ := tradeSizeRatio.Float64() var slippage float64 if tradeSizeFloat < 0.01 { // < 1% of pool // Linear approximation for small trades ratioFloat, _ := executionRatio.Float64() slippage = ratioFloat / 2.0 } else { // Full calculation for larger trades // Slippage = (marketPrice - executionPrice) / marketPrice // executionPrice = dy / dx = (y * 997) / (x * 1000 + dx * 997) // Simplified: slippage ≈ dx / (2 * (x + dx/2)) halfDx := new(big.Float).Quo(amountIn, big.NewFloat(2)) reservePlusHalfDx := new(big.Float).Add(reserveIn, halfDx) twoTimesReservePlusHalfDx := new(big.Float).Mul(reservePlusHalfDx, big.NewFloat(2)) slippageFloat := new(big.Float).Quo(amountIn, twoTimesReservePlusHalfDx) slippage, _ = slippageFloat.Float64() // Apply fee adjustment (0.3% fee adds to effective slippage) slippage += 0.003 } // Cap at 100% if slippage > 1.0 { slippage = 1.0 } else if slippage < 0 { slippage = 0 } return slippage } // 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 }