feat: Implement comprehensive Market Manager with database and logging
- Add complete Market Manager package with in-memory storage and CRUD operations - Implement arbitrage detection with profit calculations and thresholds - Add database adapter with PostgreSQL schema for persistence - Create comprehensive logging system with specialized log files - Add detailed documentation and implementation plans - Include example application and comprehensive test suite - Update Makefile with market manager build targets - Add check-implementations command for verification
This commit is contained in:
190
test/enhanced_profit_test.go
Normal file
190
test/enhanced_profit_test.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package test
|
||||
|
||||
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.NewSimpleProfitCalculator(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.NewSimpleProfitCalculator(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)
|
||||
}
|
||||
Reference in New Issue
Block a user