feat: create v2-prep branch with comprehensive planning
Restructured project for V2 refactor: **Structure Changes:** - Moved all V1 code to orig/ folder (preserved with git mv) - Created docs/planning/ directory - Added orig/README_V1.md explaining V1 preservation **Planning Documents:** - 00_V2_MASTER_PLAN.md: Complete architecture overview - Executive summary of critical V1 issues - High-level component architecture diagrams - 5-phase implementation roadmap - Success metrics and risk mitigation - 07_TASK_BREAKDOWN.md: Atomic task breakdown - 99+ hours of detailed tasks - Every task < 2 hours (atomic) - Clear dependencies and success criteria - Organized by implementation phase **V2 Key Improvements:** - Per-exchange parsers (factory pattern) - Multi-layer strict validation - Multi-index pool cache - Background validation pipeline - Comprehensive observability **Critical Issues Addressed:** - Zero address tokens (strict validation + cache enrichment) - Parsing accuracy (protocol-specific parsers) - No audit trail (background validation channel) - Inefficient lookups (multi-index cache) - Stats disconnection (event-driven metrics) Next Steps: 1. Review planning documents 2. Begin Phase 1: Foundation (P1-001 through P1-010) 3. Implement parsers in Phase 2 4. Build cache system in Phase 3 5. Add validation pipeline in Phase 4 6. Migrate and test in Phase 5 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
254
orig/pkg/marketmanager/arbitrage_test.go
Normal file
254
orig/pkg/marketmanager/arbitrage_test.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package marketmanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
func TestArbitrageDetectorCreation(t *testing.T) {
|
||||
minProfit := big.NewInt(10000000000000000) // 0.01 ETH
|
||||
minROI := 1.0 // 1%
|
||||
|
||||
detector := NewArbitrageDetector(minProfit, minROI)
|
||||
|
||||
if detector.minProfitThreshold.Cmp(minProfit) != 0 {
|
||||
t.Errorf("Expected minProfitThreshold %v, got %v", minProfit, detector.minProfitThreshold)
|
||||
}
|
||||
|
||||
if detector.minROIPercentage != minROI {
|
||||
t.Errorf("Expected minROIPercentage %f, got %f", minROI, detector.minROIPercentage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArbitrageDetectionNoOpportunity(t *testing.T) {
|
||||
minProfit := big.NewInt(10000000000000000) // 0.01 ETH
|
||||
minROI := 1.0 // 1%
|
||||
detector := NewArbitrageDetector(minProfit, minROI)
|
||||
|
||||
// Create two markets with the same price (no arbitrage opportunity)
|
||||
market1 := &Market{
|
||||
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
|
||||
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
|
||||
Price: big.NewFloat(2000.0),
|
||||
Liquidity: big.NewInt(1000000000000000000),
|
||||
SqrtPriceX96: big.NewInt(2505414483750470000),
|
||||
Tick: 200000,
|
||||
Status: StatusConfirmed,
|
||||
Fee: 3000, // 0.3%
|
||||
}
|
||||
|
||||
market2 := &Market{
|
||||
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
|
||||
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
|
||||
Price: big.NewFloat(2000.0), // Same price
|
||||
Liquidity: big.NewInt(1000000000000000000),
|
||||
SqrtPriceX96: big.NewInt(2505414483750470000),
|
||||
Tick: 200000,
|
||||
Status: StatusConfirmed,
|
||||
Fee: 3000, // 0.3%
|
||||
}
|
||||
|
||||
markets := map[string]*Market{
|
||||
"market1": market1,
|
||||
"market2": market2,
|
||||
}
|
||||
|
||||
opportunities := detector.DetectArbitrageOpportunities(markets)
|
||||
|
||||
if len(opportunities) != 0 {
|
||||
t.Errorf("Expected 0 opportunities, got %d", len(opportunities))
|
||||
}
|
||||
}
|
||||
|
||||
func TestArbitrageDetectionWithOpportunity(t *testing.T) {
|
||||
minProfit := big.NewInt(10000000000000000) // 0.01 ETH
|
||||
minROI := 0.1 // 0.1%
|
||||
detector := NewArbitrageDetector(minProfit, minROI)
|
||||
|
||||
// Create two markets with different prices (arbitrage opportunity)
|
||||
market1 := &Market{
|
||||
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
|
||||
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
|
||||
Price: big.NewFloat(2000.0),
|
||||
Liquidity: new(big.Int).Mul(big.NewInt(1000000000000000000), big.NewInt(10)), // 10 ETH - more liquidity for better profit
|
||||
SqrtPriceX96: big.NewInt(2505414483750470000),
|
||||
Tick: 200000,
|
||||
Status: StatusConfirmed,
|
||||
Fee: 3000, // 0.3%
|
||||
Key: "market1",
|
||||
RawTicker: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48_0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
}
|
||||
|
||||
market2 := &Market{
|
||||
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
|
||||
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
|
||||
Price: big.NewFloat(2100.0), // 5% higher price
|
||||
Liquidity: new(big.Int).Mul(big.NewInt(1000000000000000000), big.NewInt(10)), // 10 ETH - more liquidity for better profit
|
||||
SqrtPriceX96: big.NewInt(2568049845844280000),
|
||||
Tick: 205000,
|
||||
Status: StatusConfirmed,
|
||||
Fee: 3000, // 0.3%
|
||||
Key: "market2",
|
||||
RawTicker: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48_0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
}
|
||||
|
||||
markets := map[string]*Market{
|
||||
"market1": market1,
|
||||
"market2": market2,
|
||||
}
|
||||
|
||||
opportunities := detector.DetectArbitrageOpportunities(markets)
|
||||
|
||||
// Print some debug information
|
||||
fmt.Printf("Found %d opportunities\n", len(opportunities))
|
||||
if len(opportunities) > 0 {
|
||||
fmt.Printf("Opportunity ROI: %f\n", opportunities[0].ROI)
|
||||
fmt.Printf("Opportunity Profit: %s\n", opportunities[0].Profit.String())
|
||||
}
|
||||
|
||||
// We should find at least one opportunity
|
||||
// Note: This test might fail if the profit calculation doesn't meet thresholds
|
||||
// That's okay for now, we're just testing that the code runs without errors
|
||||
}
|
||||
|
||||
func TestArbitrageDetectionBelowThreshold(t *testing.T) {
|
||||
minProfit := big.NewInt(1000000000000000000) // 1 ETH (high threshold)
|
||||
minROI := 10.0 // 10% (high threshold)
|
||||
detector := NewArbitrageDetector(minProfit, minROI)
|
||||
|
||||
// Create two markets with a small price difference
|
||||
market1 := &Market{
|
||||
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
|
||||
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
|
||||
Price: big.NewFloat(2000.0),
|
||||
Liquidity: big.NewInt(1000000000000000000),
|
||||
SqrtPriceX96: big.NewInt(2505414483750470000),
|
||||
Tick: 200000,
|
||||
Status: StatusConfirmed,
|
||||
Fee: 3000, // 0.3%
|
||||
}
|
||||
|
||||
market2 := &Market{
|
||||
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
|
||||
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
|
||||
Price: big.NewFloat(2001.0), // Very small price difference
|
||||
Liquidity: big.NewInt(1000000000000000000),
|
||||
SqrtPriceX96: big.NewInt(2506667190854350000),
|
||||
Tick: 200050,
|
||||
Status: StatusConfirmed,
|
||||
Fee: 3000, // 0.3%
|
||||
}
|
||||
|
||||
markets := map[string]*Market{
|
||||
"market1": market1,
|
||||
"market2": market2,
|
||||
}
|
||||
|
||||
opportunities := detector.DetectArbitrageOpportunities(markets)
|
||||
|
||||
// With high thresholds, we should find no opportunities
|
||||
if len(opportunities) != 0 {
|
||||
t.Errorf("Expected 0 opportunities due to high thresholds, got %d", len(opportunities))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateOptimalTradeSize(t *testing.T) {
|
||||
minProfit := big.NewInt(10000000000000000) // 0.01 ETH
|
||||
minROI := 1.0 // 1%
|
||||
detector := NewArbitrageDetector(minProfit, minROI)
|
||||
|
||||
market1 := &Market{
|
||||
Liquidity: big.NewInt(1000000000000000000), // 1 ETH
|
||||
}
|
||||
|
||||
market2 := &Market{
|
||||
Liquidity: big.NewInt(500000000000000000), // 0.5 ETH
|
||||
}
|
||||
|
||||
// Test optimal trade size calculation
|
||||
optimalSize := detector.calculateOptimalTradeSize(market1, market2)
|
||||
|
||||
// Should be 1% of the smaller liquidity (0.5 ETH * 0.01 = 0.005 ETH)
|
||||
expected := big.NewInt(5000000000000000) // 0.005 ETH in wei
|
||||
if optimalSize.Cmp(expected) != 0 {
|
||||
t.Errorf("Expected optimal size %v, got %v", expected, optimalSize)
|
||||
}
|
||||
|
||||
// Test with very small liquidity
|
||||
market3 := &Market{
|
||||
Liquidity: big.NewInt(100000000000000000), // 0.1 ETH
|
||||
}
|
||||
|
||||
market4 := &Market{
|
||||
Liquidity: big.NewInt(200000000000000000), // 0.2 ETH
|
||||
}
|
||||
|
||||
optimalSize = detector.calculateOptimalTradeSize(market3, market4)
|
||||
|
||||
// Should be minimum trade size (0.001 ETH) since 1% of 0.1 ETH is 0.001 ETH
|
||||
minTradeSize := big.NewInt(1000000000000000) // 0.001 ETH in wei
|
||||
if optimalSize.Cmp(minTradeSize) != 0 {
|
||||
t.Errorf("Expected minimum trade size %v, got %v", minTradeSize, optimalSize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculatePriceImpact(t *testing.T) {
|
||||
minProfit := big.NewInt(10000000000000000) // 0.01 ETH
|
||||
minROI := 1.0 // 1%
|
||||
detector := NewArbitrageDetector(minProfit, minROI)
|
||||
|
||||
market := &Market{
|
||||
Liquidity: big.NewInt(1000000000000000000), // 1 ETH
|
||||
}
|
||||
|
||||
// Test price impact calculation
|
||||
tradeSize := big.NewInt(100000000000000000) // 0.1 ETH
|
||||
impact := detector.calculatePriceImpact(tradeSize, market)
|
||||
|
||||
// 0.1 ETH / 1 ETH = 0.1 (10%) utilization
|
||||
// Impact should be 0.1 * (1 + 0.1) = 0.11 (11%)
|
||||
// But we cap at 10% so it should be 0.1
|
||||
expected := 0.1
|
||||
actual, _ := impact.Float64()
|
||||
|
||||
// Allow for small floating point differences
|
||||
if actual < expected*0.99 || actual > expected*1.01 {
|
||||
t.Errorf("Expected impact ~%f, got %f", expected, actual)
|
||||
}
|
||||
|
||||
// Test with zero liquidity (should return default impact)
|
||||
marketZero := &Market{
|
||||
Liquidity: big.NewInt(0),
|
||||
}
|
||||
|
||||
impact = detector.calculatePriceImpact(tradeSize, marketZero)
|
||||
expectedDefault := 0.01 // 1% default
|
||||
actual, _ = impact.Float64()
|
||||
|
||||
if actual != expectedDefault {
|
||||
t.Errorf("Expected default impact %f, got %f", expectedDefault, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEstimateGasCost(t *testing.T) {
|
||||
minProfit := big.NewInt(10000000000000000) // 0.01 ETH
|
||||
minROI := 1.0 // 1%
|
||||
detector := NewArbitrageDetector(minProfit, minROI)
|
||||
|
||||
market1 := &Market{}
|
||||
market2 := &Market{}
|
||||
|
||||
// Test gas cost estimation
|
||||
gasCost := detector.estimateGasCost(market1, market2)
|
||||
|
||||
// Base gas (250000) * (2 gwei + 5 gwei priority) = 250000 * 7 gwei
|
||||
// 250000 * 7000000000 = 1750000000000000 wei = 0.00175 ETH
|
||||
expected := big.NewInt(1750000000000000)
|
||||
|
||||
if gasCost.Cmp(expected) != 0 {
|
||||
t.Errorf("Expected gas cost %v, got %v", expected, gasCost)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user