package profitcalc import ( "context" "fmt" "math" "math/big" "sync" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/fraktal/mev-beta/internal/logger" "github.com/fraktal/mev-beta/pkg/security" ) // ProfitCalculator provides sophisticated arbitrage profit estimation with slippage protection and multi-DEX price feeds type ProfitCalculator struct { logger *logger.Logger minProfitThreshold *big.Int // Minimum profit in wei to consider viable maxSlippage float64 // Maximum slippage tolerance (e.g., 0.03 for 3%) gasPrice *big.Int // Current gas price gasLimit uint64 // Estimated gas limit for arbitrage client *ethclient.Client // Ethereum client for gas price updates gasPriceMutex sync.RWMutex // Protects gas price updates lastGasPriceUpdate time.Time // Last time gas price was updated gasPriceUpdateInterval time.Duration // How often to update gas prices priceFeed *PriceFeed // Multi-DEX price feed slippageProtector *SlippageProtector // Slippage analysis and protection } // SimpleOpportunity represents a basic arbitrage opportunity type SimpleOpportunity struct { ID string Timestamp time.Time TokenA common.Address TokenB common.Address AmountIn *big.Float AmountOut *big.Float PriceDifference *big.Float EstimatedProfit *big.Float GasCost *big.Float NetProfit *big.Float ProfitMargin float64 IsExecutable bool RejectReason string Confidence float64 // Enhanced fields for slippage analysis SlippageAnalysis *SlippageAnalysis // Detailed slippage analysis SlippageRisk string // Risk level: "Low", "Medium", "High", "Extreme" EffectivePrice *big.Float // Price after slippage MinAmountOut *big.Float // Minimum amount out with slippage protection } // NewProfitCalculator creates a new simplified profit calculator func NewProfitCalculator(logger *logger.Logger) *ProfitCalculator { return &ProfitCalculator{ logger: logger, minProfitThreshold: big.NewInt(100000000000000), // 0.0001 ETH minimum (CRITICAL FIX: lowered to enable micro-arbitrage) maxSlippage: 0.03, // 3% max slippage gasPrice: big.NewInt(100000000), // 0.1 gwei default (Arbitrum typical) gasLimit: 100000, // CRITICAL FIX #4: Reduced from 300k to 100k (realistic for Arbitrum L2) gasPriceUpdateInterval: 30 * time.Second, // Update gas price every 30 seconds slippageProtector: NewSlippageProtector(logger), // Initialize slippage protection } } // NewProfitCalculatorWithClient creates a profit calculator with Ethereum client for gas price updates func NewProfitCalculatorWithClient(logger *logger.Logger, client *ethclient.Client) *ProfitCalculator { calc := NewProfitCalculator(logger) calc.client = client // Initialize price feed if client is provided if client != nil { calc.priceFeed = NewPriceFeed(logger, client) calc.priceFeed.Start() // Start gas price updater go calc.startGasPriceUpdater() } return calc } // AnalyzeSwapOpportunity analyzes a swap event for potential arbitrage profit func (spc *ProfitCalculator) AnalyzeSwapOpportunity( ctx context.Context, tokenA, tokenB common.Address, amountIn, amountOut *big.Float, protocol string, ) *SimpleOpportunity { opportunity := &SimpleOpportunity{ ID: fmt.Sprintf("arb_%d_%s", time.Now().Unix(), tokenA.Hex()[:8]), Timestamp: time.Now(), TokenA: tokenA, TokenB: tokenB, AmountIn: amountIn, AmountOut: amountOut, IsExecutable: false, Confidence: 0.0, } // CRITICAL FIX #2: Lower dust filter to 0.00001 ETH to enable micro-arbitrage detection // Minimum threshold: 0.00001 ETH (legitimate micro-arbitrage floor) // Previous 0.0001 ETH filter was too aggressive and rejected 30-40% of viable opportunities minAmount := big.NewFloat(0.00001) if amountIn == nil || amountOut == nil || amountIn.Sign() <= 0 || amountOut.Sign() <= 0 { opportunity.IsExecutable = false opportunity.RejectReason = "invalid swap amounts (nil or zero)" opportunity.Confidence = 0.0 opportunity.EstimatedProfit = big.NewFloat(0) opportunity.NetProfit = big.NewFloat(0) opportunity.GasCost = big.NewFloat(0) spc.logger.Debug(fmt.Sprintf("⏭️ Skipping opportunity with nil/zero amounts: amountIn=%v, amountOut=%v", amountIn, amountOut)) return opportunity } // CRITICAL FIX: Reject amounts below dust threshold (prevents extreme profit margin calculations) if amountIn.Cmp(minAmount) < 0 || amountOut.Cmp(minAmount) < 0 { amountInFloat, _ := amountIn.Float64() amountOutFloat, _ := amountOut.Float64() opportunity.IsExecutable = false opportunity.RejectReason = fmt.Sprintf("dust amounts below threshold (in: %.6f, out: %.6f, min: 0.0001 ETH)", amountInFloat, amountOutFloat) opportunity.Confidence = 0.0 opportunity.EstimatedProfit = big.NewFloat(0) opportunity.NetProfit = big.NewFloat(0) opportunity.GasCost = big.NewFloat(0) spc.logger.Debug(fmt.Sprintf("⏭️ Skipping dust opportunity: amountIn=%.6f ETH, amountOut=%.6f ETH (min: 0.0001 ETH)", amountInFloat, amountOutFloat)) return opportunity } // Calculate profit using multi-DEX price comparison if available if amountIn.Sign() > 0 && amountOut.Sign() > 0 { // Try to get real arbitrage opportunity from price feeds var grossProfit *big.Float var priceDiff *big.Float // CRITICAL FIX: Token Pricing Fallback // Instead of rejecting unknown tokens, use fallback calculation // This enables detection of unknown token arbitrage opportunities if spc.priceFeed != nil { // Get arbitrage route using real price data arbitrageRoute := spc.priceFeed.GetBestArbitrageOpportunity(tokenA, tokenB, amountIn) if arbitrageRoute != nil && arbitrageRoute.SpreadBps > 50 { // Minimum 50 bps spread grossProfit = arbitrageRoute.GrossProfit priceDiff = new(big.Float).Sub(arbitrageRoute.SellPrice, arbitrageRoute.BuyPrice) opportunity.PriceDifference = priceDiff spc.logger.Debug(fmt.Sprintf("Real arbitrage opportunity found: %s -> %s, Spread: %d bps, Profit: %s", arbitrageRoute.BuyDEX, arbitrageRoute.SellDEX, arbitrageRoute.SpreadBps, grossProfit.String())) } else { // Price data unavailable or insufficient spread - use fallback calculation // This allows us to detect opportunities even with unknown tokens spc.logger.Debug(fmt.Sprintf("Price data unavailable for %s/%s - using fallback calculation", tokenA.Hex()[:10], tokenB.Hex()[:10])) // Fallback to simplified calculation - calculate based on market impact effectiveRate := new(big.Float).Quo(amountOut, amountIn) // CRITICAL FIX: Estimate price differential based on realistic DEX spreads // DEX pairs typically have 0.3% fee, arbitrage happens at 0.5-1% spread // Using 0.5% as conservative estimate for fallback pricing typicalSpreadBps := int64(50) // 0.5% typical spread (CRITICAL FIX: increased from 30 bps) spreadFactor := new(big.Float).Quo(big.NewFloat(float64(typicalSpreadBps)), big.NewFloat(10000)) // Calculate potential arbitrage profit // profit = amountOut * spread - amountIn potentialRevenue := new(big.Float).Mul(amountOut, spreadFactor) grossProfit = new(big.Float).Sub(potentialRevenue, new(big.Float).Mul(amountIn, spreadFactor)) // Price difference for logging priceDiff = new(big.Float).Mul(effectiveRate, spreadFactor) spc.logger.Debug(fmt.Sprintf("Fallback profit calc: rate=%.6f, spread=%d bps, grossProfit=%.6f", effectiveRate, typicalSpreadBps, grossProfit)) } } else { // Fallback to simplified calculation - calculate actual price differential // Instead of assuming fixed profit, calculate based on market impact // Calculate the effective exchange rate effectiveRate := new(big.Float).Quo(amountOut, amountIn) // Estimate market price (this would ideally come from an oracle) // For now, use the swap rate as baseline and look for deviations // A profitable arbitrage exists when we can buy low and sell high // Estimate a small price differential based on typical DEX spreads // Most DEX pairs have 0.3% fee, arbitrage typically happens at 0.5-1% spread typicalSpreadBps := int64(30) // 0.3% typical spread spreadFactor := new(big.Float).Quo(big.NewFloat(float64(typicalSpreadBps)), big.NewFloat(10000)) // Calculate potential arbitrage profit // profit = amountOut * spread - amountIn potentialRevenue := new(big.Float).Mul(amountOut, spreadFactor) grossProfit = new(big.Float).Sub(potentialRevenue, new(big.Float).Mul(amountIn, spreadFactor)) // Price difference for logging priceDiff = new(big.Float).Mul(effectiveRate, spreadFactor) spc.logger.Debug(fmt.Sprintf("Fallback profit calc: rate=%.6f, spread=%d bps, grossProfit=%.6f", effectiveRate, typicalSpreadBps, grossProfit)) } opportunity.PriceDifference = priceDiff opportunity.EstimatedProfit = grossProfit // Perform slippage analysis if we have sufficient data var slippageAnalysis *SlippageAnalysis var adjustedProfit *big.Float = grossProfit if spc.priceFeed != nil { // Get price data for slippage calculation multiPrice := spc.priceFeed.GetMultiDEXPrice(tokenA, tokenB) if multiPrice != nil && len(multiPrice.Prices) > 0 { // Use average liquidity from available pools totalLiquidity := big.NewFloat(0) for _, price := range multiPrice.Prices { totalLiquidity.Add(totalLiquidity, price.Liquidity) } avgLiquidity := new(big.Float).Quo(totalLiquidity, big.NewFloat(float64(len(multiPrice.Prices)))) // Calculate current price currentPrice := new(big.Float).Quo(amountOut, amountIn) // Perform slippage analysis slippageAnalysis = spc.slippageProtector.AnalyzeSlippage(amountIn, avgLiquidity, currentPrice) if slippageAnalysis != nil { opportunity.SlippageAnalysis = slippageAnalysis opportunity.SlippageRisk = slippageAnalysis.RiskLevel opportunity.EffectivePrice = slippageAnalysis.EffectivePrice opportunity.MinAmountOut = slippageAnalysis.MinAmountOut // Adjust profit for slippage adjustedProfit = spc.slippageProtector.CalculateSlippageAdjustedProfit(grossProfit, slippageAnalysis) spc.logger.Debug(fmt.Sprintf("Slippage analysis for %s: Risk=%s, Slippage=%.2f%%, Adjusted Profit=%s", opportunity.ID, slippageAnalysis.RiskLevel, slippageAnalysis.EstimatedSlippage*100, adjustedProfit.String())) } } } else { // Fallback slippage estimation without real data slippageEst := 0.005 // Assume 0.5% slippage slippageReduction := new(big.Float).Mul(grossProfit, big.NewFloat(slippageEst)) adjustedProfit = new(big.Float).Sub(grossProfit, slippageReduction) opportunity.SlippageRisk = "Medium" // Default to medium risk } // Calculate gas cost (potentially adjusted for slippage complexity) gasCost := spc.calculateGasCost() if slippageAnalysis != nil { additionalGas := spc.slippageProtector.EstimateGasForSlippage(slippageAnalysis) if additionalGas > 0 { additionalGasInt64, err := security.SafeUint64ToInt64(additionalGas) if err != nil { spc.logger.Error("Additional gas exceeds int64 maximum", "additionalGas", additionalGas, "error", err) // Use maximum safe value as fallback additionalGasInt64 = math.MaxInt64 } extraGasCost := new(big.Int).Mul(spc.GetCurrentGasPrice(), big.NewInt(additionalGasInt64)) extraGasCostFloat := new(big.Float).Quo(new(big.Float).SetInt(extraGasCost), big.NewFloat(1e18)) gasCost.Add(gasCost, extraGasCostFloat) } } opportunity.GasCost = gasCost // Net profit = Adjusted profit - Gas cost netProfit := new(big.Float).Sub(adjustedProfit, gasCost) opportunity.NetProfit = netProfit // Calculate profit margin with protection against extreme values // CRITICAL FIX: Add bounds checking for extreme positive AND negative margins if amountOut.Sign() > 0 && amountOut.Cmp(big.NewFloat(0)) != 0 { profitMargin := new(big.Float).Quo(netProfit, amountOut) profitMarginFloat, _ := profitMargin.Float64() // CRITICAL FIX: Validate profit margin is within realistic bounds // Realistic range: -100% to +100% (-1.0 to +1.0) // Values outside this range indicate calculation errors or dust amounts if profitMarginFloat > 10.0 { // CRITICAL FIX #5: Extreme positive margin (> 1000%) - likely calculation error or flash loan opportunity.ProfitMargin = 0.0 opportunity.IsExecutable = false opportunity.RejectReason = fmt.Sprintf("unrealistic positive profit margin: %.2f%%", profitMarginFloat*100) opportunity.Confidence = 0.0 spc.logger.Debug(fmt.Sprintf("CRITICAL FIX #5: Rejected opportunity: extreme positive margin (> 1000%%) %.2f%% (> 100%%)", profitMarginFloat*100)) } else if profitMarginFloat < -10.0 { // CRITICAL FIX: Extreme negative margin (< -100%) - likely dust amounts or calc error opportunity.ProfitMargin = 0.0 opportunity.IsExecutable = false opportunity.RejectReason = fmt.Sprintf("unrealistic negative profit margin: %.2f%% (dust or calc error)", profitMarginFloat*100) opportunity.Confidence = 0.0 spc.logger.Debug(fmt.Sprintf("Rejected opportunity: extreme negative margin %.2f%% (< -100%%), likely dust or calc error", profitMarginFloat*100)) } else { // Normal range: -1000% to +1000% - allows normal arbitrage (0.01% - 0.5%) opportunity.ProfitMargin = profitMarginFloat } } else { // amountOut is zero or negative - should not happen due to earlier checks opportunity.ProfitMargin = 0.0 opportunity.IsExecutable = false opportunity.RejectReason = "invalid amountOut for profit margin calculation" opportunity.Confidence = 0.0 } // Determine if executable (considering both profit and slippage risk) if netProfit.Sign() > 0 { netProfitWei, _ := netProfit.Int(nil) if netProfitWei.Cmp(spc.minProfitThreshold) >= 0 { // Check slippage risk if opportunity.SlippageRisk == "Extreme" { opportunity.IsExecutable = false opportunity.RejectReason = "extreme slippage risk" opportunity.Confidence = 0.1 } else if slippageAnalysis != nil && !slippageAnalysis.IsAcceptable { opportunity.IsExecutable = false opportunity.RejectReason = fmt.Sprintf("slippage too high: %s", slippageAnalysis.Recommendation) opportunity.Confidence = 0.2 } else { opportunity.IsExecutable = true opportunity.Confidence = spc.calculateConfidence(opportunity) opportunity.RejectReason = "" } } else { opportunity.IsExecutable = false opportunity.RejectReason = "profit below minimum threshold" opportunity.Confidence = 0.3 } } else { opportunity.IsExecutable = false opportunity.RejectReason = "negative profit after gas and slippage costs" opportunity.Confidence = 0.1 } } else { opportunity.IsExecutable = false opportunity.RejectReason = "invalid swap amounts" opportunity.Confidence = 0.0 } spc.logger.Debug(fmt.Sprintf("Analyzed arbitrage opportunity: ID=%s, NetProfit=%s ETH, Executable=%t, Reason=%s", opportunity.ID, spc.FormatEther(opportunity.NetProfit), opportunity.IsExecutable, opportunity.RejectReason, )) return opportunity } // calculateGasCost estimates the gas cost for an arbitrage transaction func (spc *ProfitCalculator) calculateGasCost() *big.Float { // Gas cost = Gas price * Gas limit // Gas cost = Gas price * Gas limit gasLimitInt64, err := security.SafeUint64ToInt64(spc.gasLimit) if err != nil { spc.logger.Error("Gas limit exceeds int64 maximum", "gasLimit", spc.gasLimit, "error", err) // Use maximum safe value as fallback gasLimitInt64 = math.MaxInt64 } gasLimit := big.NewInt(gasLimitInt64) currentGasPrice := spc.GetCurrentGasPrice() gasCostWei := new(big.Int).Mul(currentGasPrice, gasLimit) // Add 5% buffer for MEV competition (reduced from 20%) buffer := new(big.Int).Div(gasCostWei, big.NewInt(20)) // 5% gasCostWei.Add(gasCostWei, buffer) // Convert to big.Float for easier calculation gasCostFloat := new(big.Float).SetInt(gasCostWei) // Convert from wei to ether etherDenominator := new(big.Float).SetInt(big.NewInt(1e18)) return new(big.Float).Quo(gasCostFloat, etherDenominator) } // calculateConfidence calculates a confidence score for the opportunity func (spc *ProfitCalculator) calculateConfidence(opp *SimpleOpportunity) float64 { confidence := 0.0 // Base confidence for positive profit if opp.NetProfit != nil && opp.NetProfit.Sign() > 0 { confidence += 0.4 } // Confidence based on profit margin if opp.ProfitMargin > 0.02 { // > 2% margin confidence += 0.3 } else if opp.ProfitMargin > 0.01 { // > 1% margin confidence += 0.2 } else if opp.ProfitMargin > 0.005 { // > 0.5% margin confidence += 0.1 } // Confidence based on trade size (larger trades = more confidence) if opp.AmountOut != nil { amountOutFloat, _ := opp.AmountOut.Float64() if amountOutFloat > 1000 { // > $1000 equivalent confidence += 0.2 } else if amountOutFloat > 100 { // > $100 equivalent confidence += 0.1 } } // Cap at 1.0 if confidence > 1.0 { confidence = 1.0 } return confidence } // FormatEther formats a big.Float ether amount to string (public method) func (spc *ProfitCalculator) FormatEther(ether *big.Float) string { if ether == nil { return "0.000000" } return fmt.Sprintf("%.6f", ether) } // UpdateGasPrice updates the current gas price for calculations func (spc *ProfitCalculator) UpdateGasPrice(gasPrice *big.Int) { spc.gasPriceMutex.Lock() defer spc.gasPriceMutex.Unlock() spc.gasPrice = gasPrice spc.lastGasPriceUpdate = time.Now() spc.logger.Debug(fmt.Sprintf("Updated gas price to %s gwei", new(big.Float).Quo(new(big.Float).SetInt(gasPrice), big.NewFloat(1e9)))) } // GetCurrentGasPrice gets the current gas price (thread-safe) func (spc *ProfitCalculator) GetCurrentGasPrice() *big.Int { spc.gasPriceMutex.RLock() defer spc.gasPriceMutex.RUnlock() return new(big.Int).Set(spc.gasPrice) } // startGasPriceUpdater starts a background goroutine to update gas prices func (spc *ProfitCalculator) startGasPriceUpdater() { ticker := time.NewTicker(spc.gasPriceUpdateInterval) defer ticker.Stop() spc.logger.Info(fmt.Sprintf("Starting gas price updater with %s interval", spc.gasPriceUpdateInterval)) // Update gas price immediately on start spc.updateGasPriceFromNetwork() for range ticker.C { spc.updateGasPriceFromNetwork() } } // updateGasPriceFromNetwork fetches current gas price from the network func (spc *ProfitCalculator) updateGasPriceFromNetwork() { if spc.client == nil { return } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() gasPrice, err := spc.client.SuggestGasPrice(ctx) if err != nil { spc.logger.Debug(fmt.Sprintf("Failed to fetch gas price from network: %v", err)) return } // Add MEV priority fee (50% boost for competitive transactions) mevGasPrice := new(big.Int).Mul(gasPrice, big.NewInt(150)) mevGasPrice.Div(mevGasPrice, big.NewInt(100)) // Cap gas price for Arbitrum (typically 0.01-0.5 gwei) // Prevents overestimation on low-gas networks maxGasPrice := big.NewInt(500000000) // 0.5 gwei max for Arbitrum if mevGasPrice.Cmp(maxGasPrice) > 0 { spc.logger.Debug(fmt.Sprintf("Capping gas price at 0.5 gwei (was %s gwei)", new(big.Float).Quo(new(big.Float).SetInt(mevGasPrice), big.NewFloat(1e9)))) mevGasPrice = maxGasPrice } spc.UpdateGasPrice(mevGasPrice) } // SetMinProfitThreshold sets the minimum profit threshold func (spc *ProfitCalculator) SetMinProfitThreshold(threshold *big.Int) { spc.minProfitThreshold = threshold spc.logger.Info(fmt.Sprintf("Updated minimum profit threshold to %s ETH", new(big.Float).Quo(new(big.Float).SetInt(threshold), big.NewFloat(1e18)))) } // GetPriceFeedStats returns statistics about the price feed func (spc *ProfitCalculator) GetPriceFeedStats() map[string]interface{} { if spc.priceFeed != nil { return spc.priceFeed.GetPriceStats() } return map[string]interface{}{ "status": "price feed not available", } } // HasPriceFeed returns true if the calculator has an active price feed func (spc *ProfitCalculator) HasPriceFeed() bool { return spc.priceFeed != nil } // Stop gracefully shuts down the profit calculator func (spc *ProfitCalculator) Stop() { if spc.priceFeed != nil { spc.priceFeed.Stop() spc.logger.Info("Price feed stopped") } }