//go:build integration && legacy && forked // +build integration,legacy,forked package test_main import ( "context" "math/big" "testing" "time" "github.com/ethereum/go-ethereum/common" "github.com/fraktal/mev-beta/internal/logger" "github.com/fraktal/mev-beta/pkg/profitcalc" ) func TestEnhancedProfitCalculationAndRanking(t *testing.T) { // Create a test logger log := logger.New("debug", "text", "") // Create profit calculator and ranker calc := profitcalc.NewProfitCalculator(log) ranker := profitcalc.NewOpportunityRanker(log) // Test tokens wethAddr := common.HexToAddress("0x82af49447d8a07e3bd95bd0d56f35241523fbab1") usdcAddr := common.HexToAddress("0xaf88d065e77c8cc2239327c5edb3a432268e5831") arbAddr := common.HexToAddress("0x912ce59144191c1204e64559fe8253a0e49e6548") // Create multiple test opportunities with different characteristics opportunities := []*profitcalc.SimpleOpportunity{ // High profit, high confidence opportunity calc.AnalyzeSwapOpportunity( context.Background(), wethAddr, usdcAddr, big.NewFloat(5.0), // 5 ETH big.NewFloat(10000.0), // 10k USDC "UniswapV3", ), // Medium profit opportunity calc.AnalyzeSwapOpportunity( context.Background(), wethAddr, arbAddr, big.NewFloat(1.0), // 1 ETH big.NewFloat(2500.0), // 2.5k ARB "SushiSwap", ), // Lower profit opportunity calc.AnalyzeSwapOpportunity( context.Background(), usdcAddr, arbAddr, big.NewFloat(100.0), // 100 USDC big.NewFloat(250.0), // 250 ARB "TraderJoe", ), // Very small opportunity (should be filtered out) calc.AnalyzeSwapOpportunity( context.Background(), wethAddr, usdcAddr, big.NewFloat(0.001), // 0.001 ETH big.NewFloat(2.0), // 2 USDC "PancakeSwap", ), } // Add opportunities to ranker var rankedOpps []*profitcalc.RankedOpportunity for i, opp := range opportunities { if opp != nil { rankedOpp := ranker.AddOpportunity(opp) if rankedOpp != nil { rankedOpps = append(rankedOpps, rankedOpp) t.Logf("Added Opportunity %d: ID=%s, NetProfit=%s ETH, ProfitMargin=%.4f%%, Confidence=%.2f", i+1, opp.ID, calc.FormatEther(opp.NetProfit), opp.ProfitMargin*100, opp.Confidence) } else { t.Logf("Opportunity %d filtered out: ID=%s", i+1, opp.ID) } } } // Get top opportunities topOpps := ranker.GetTopOpportunities(3) t.Logf("\n=== Top 3 Opportunities ===") for _, opp := range topOpps { t.Logf("Rank %d: ID=%s, Score=%.4f, NetProfit=%s ETH, ProfitMargin=%.4f%%, Confidence=%.2f, Competition=%.2f", opp.Rank, opp.ID, opp.Score, calc.FormatEther(opp.NetProfit), opp.ProfitMargin*100, opp.Confidence, opp.CompetitionRisk) } // Get executable opportunities executable := ranker.GetExecutableOpportunities(5) t.Logf("\n=== Executable Opportunities ===") for _, opp := range executable { t.Logf("ID=%s, Executable=%t, Reason=%s, Score=%.4f", opp.ID, opp.IsExecutable, opp.RejectReason, opp.Score) } // Get statistics stats := ranker.GetStats() t.Logf("\n=== Ranking Statistics ===") t.Logf("Total Opportunities: %v", stats["totalOpportunities"]) t.Logf("Executable Opportunities: %v", stats["executableOpportunities"]) t.Logf("Average Score: %.4f", stats["averageScore"]) t.Logf("Average Confidence: %.4f", stats["averageConfidence"]) // Verify ranking behavior if len(topOpps) > 1 { // First ranked opportunity should have highest score if topOpps[0].Score < topOpps[1].Score { t.Errorf("Ranking error: first opportunity (%.4f) should have higher score than second (%.4f)", topOpps[0].Score, topOpps[1].Score) } } // Test opportunity updates t.Logf("\n=== Testing Opportunity Updates ===") if len(opportunities) > 0 && opportunities[0] != nil { // Create a similar opportunity (same tokens, similar amount) similarOpp := calc.AnalyzeSwapOpportunity( context.Background(), opportunities[0].TokenA, opportunities[0].TokenB, big.NewFloat(5.1), // Slightly different amount big.NewFloat(10200.0), "UniswapV3", ) if similarOpp != nil { rankedSimilar := ranker.AddOpportunity(similarOpp) if rankedSimilar != nil { t.Logf("Updated similar opportunity: UpdateCount=%d", rankedSimilar.UpdateCount) if rankedSimilar.UpdateCount < 2 { t.Errorf("Expected UpdateCount >= 2, got %d", rankedSimilar.UpdateCount) } } } } } func TestOpportunityAging(t *testing.T) { // Create a test logger log := logger.New("debug", "text", "") // Create profit calculator and ranker with short TTL for testing calc := profitcalc.NewProfitCalculator(log) ranker := profitcalc.NewOpportunityRanker(log) // Create a test opportunity wethAddr := common.HexToAddress("0x82af49447d8a07e3bd95bd0d56f35241523fbab1") usdcAddr := common.HexToAddress("0xaf88d065e77c8cc2239327c5edb3a432268e5831") opp := calc.AnalyzeSwapOpportunity( context.Background(), wethAddr, usdcAddr, big.NewFloat(1.0), big.NewFloat(2000.0), "UniswapV3", ) if opp == nil { t.Fatal("Failed to create test opportunity") } // Add opportunity rankedOpp := ranker.AddOpportunity(opp) if rankedOpp == nil { t.Fatal("Failed to add opportunity to ranker") } initialScore := rankedOpp.Score t.Logf("Initial opportunity score: %.4f", initialScore) // Wait a moment and check that freshness affects score time.Sleep(100 * time.Millisecond) // Re-rank to update scores based on age topOpps := ranker.GetTopOpportunities(1) if len(topOpps) > 0 { newScore := topOpps[0].Score t.Logf("Score after aging: %.4f", newScore) // Score should decrease due to freshness component (though might be minimal for 100ms) if newScore > initialScore { t.Logf("Note: Score increased slightly, this is normal for short time periods") } } // Test stats stats := ranker.GetStats() t.Logf("Ranker stats: %+v", stats) }