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:
215
orig/pkg/marketmanager/arbitrage.go
Normal file
215
orig/pkg/marketmanager/arbitrage.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package marketmanager
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/fraktal/mev-beta/pkg/types"
|
||||
)
|
||||
|
||||
// ArbitrageDetector detects arbitrage opportunities between markets
|
||||
type ArbitrageDetector struct {
|
||||
minProfitThreshold *big.Int // Minimum profit threshold in wei
|
||||
minROIPercentage float64 // Minimum ROI percentage
|
||||
}
|
||||
|
||||
// NewArbitrageDetector creates a new arbitrage detector
|
||||
func NewArbitrageDetector(minProfitThreshold *big.Int, minROIPercentage float64) *ArbitrageDetector {
|
||||
return &ArbitrageDetector{
|
||||
minProfitThreshold: minProfitThreshold,
|
||||
minROIPercentage: minROIPercentage,
|
||||
}
|
||||
}
|
||||
|
||||
// MarketOpportunity represents market-specific arbitrage data (extends canonical ArbitrageOpportunity)
|
||||
type MarketOpportunity struct {
|
||||
*types.ArbitrageOpportunity
|
||||
Market1 *Market
|
||||
Market2 *Market
|
||||
}
|
||||
|
||||
// DetectArbitrageOpportunities detects arbitrage opportunities among markets with the same rawTicker
|
||||
func (ad *ArbitrageDetector) DetectArbitrageOpportunities(markets map[string]*Market) []*types.ArbitrageOpportunity {
|
||||
var opportunities []*types.ArbitrageOpportunity
|
||||
|
||||
// Convert map to slice for sorting
|
||||
marketList := make([]*Market, 0, len(markets))
|
||||
for _, market := range markets {
|
||||
if market.IsValid() {
|
||||
marketList = append(marketList, market)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort markets by price (lowest to highest)
|
||||
sort.Slice(marketList, func(i, j int) bool {
|
||||
return marketList[i].Price.Cmp(marketList[j].Price) < 0
|
||||
})
|
||||
|
||||
// Check each combination for arbitrage opportunities
|
||||
for i := 0; i < len(marketList); i++ {
|
||||
for j := i + 1; j < len(marketList); j++ {
|
||||
market1 := marketList[i]
|
||||
market2 := marketList[j]
|
||||
|
||||
// Check if there's an arbitrage opportunity
|
||||
opportunity := ad.checkArbitrageOpportunity(market1, market2)
|
||||
if opportunity != nil {
|
||||
opportunities = append(opportunities, opportunity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return opportunities
|
||||
}
|
||||
|
||||
// checkArbitrageOpportunity checks if there's an arbitrage opportunity between two markets
|
||||
func (ad *ArbitrageDetector) checkArbitrageOpportunity(market1, market2 *Market) *types.ArbitrageOpportunity {
|
||||
// Calculate price difference
|
||||
priceDiff := new(big.Float).Sub(market2.Price, market1.Price)
|
||||
if priceDiff.Sign() <= 0 {
|
||||
return nil // No profit opportunity
|
||||
}
|
||||
|
||||
// Calculate relative price difference (profit margin)
|
||||
relativeDiff := new(big.Float).Quo(priceDiff, market1.Price)
|
||||
|
||||
// Estimate optimal trade size based on liquidity
|
||||
optimalTradeSize := ad.calculateOptimalTradeSize(market1, market2)
|
||||
|
||||
// Calculate price impact on both markets
|
||||
impact1 := ad.calculatePriceImpact(optimalTradeSize, market1)
|
||||
impact2 := ad.calculatePriceImpact(optimalTradeSize, market2)
|
||||
|
||||
// Adjusted profit after price impact
|
||||
adjustedRelativeDiff := new(big.Float).Sub(relativeDiff, new(big.Float).Add(impact1, impact2))
|
||||
if adjustedRelativeDiff.Sign() <= 0 {
|
||||
return nil // No profit after price impact
|
||||
}
|
||||
|
||||
// Calculate gross profit
|
||||
grossProfit := new(big.Float).Mul(new(big.Float).SetInt(optimalTradeSize), adjustedRelativeDiff)
|
||||
|
||||
// Convert to wei for integer calculations
|
||||
grossProfitWei := new(big.Int)
|
||||
grossProfit.Int(grossProfitWei)
|
||||
|
||||
// Estimate gas costs
|
||||
gasCost := ad.estimateGasCost(market1, market2)
|
||||
|
||||
// Calculate net profit
|
||||
netProfit := new(big.Int).Sub(grossProfitWei, gasCost)
|
||||
|
||||
// Check if profit meets minimum threshold
|
||||
if netProfit.Cmp(ad.minProfitThreshold) < 0 {
|
||||
return nil // Profit too low
|
||||
}
|
||||
|
||||
// Calculate ROI
|
||||
var roi float64
|
||||
if optimalTradeSize.Sign() > 0 {
|
||||
roiFloat := new(big.Float).Quo(new(big.Float).SetInt(netProfit), new(big.Float).SetInt(optimalTradeSize))
|
||||
roi, _ = roiFloat.Float64()
|
||||
roi *= 100 // Convert to percentage
|
||||
}
|
||||
|
||||
// Check if ROI meets minimum threshold
|
||||
if roi < ad.minROIPercentage {
|
||||
return nil // ROI too low
|
||||
}
|
||||
|
||||
// Create canonical arbitrage opportunity
|
||||
return &types.ArbitrageOpportunity{
|
||||
Path: []string{market1.Token0.Hex(), market1.Token1.Hex()},
|
||||
Pools: []string{"market1-pool", "market2-pool"},
|
||||
AmountIn: optimalTradeSize,
|
||||
Profit: netProfit,
|
||||
NetProfit: netProfit,
|
||||
GasEstimate: gasCost,
|
||||
ROI: roi,
|
||||
Protocol: "market-arbitrage",
|
||||
ExecutionTime: 5000, // 5 seconds
|
||||
Confidence: 0.9, // High confidence for market data
|
||||
PriceImpact: 0.01, // 1% estimated
|
||||
MaxSlippage: 0.02, // 2% max slippage
|
||||
TokenIn: market1.Token0,
|
||||
TokenOut: market1.Token1,
|
||||
Timestamp: time.Now().Unix(),
|
||||
Risk: 0.1, // Low risk for market arbitrage
|
||||
}
|
||||
}
|
||||
|
||||
// calculateOptimalTradeSize calculates the optimal trade size for maximum profit
|
||||
func (ad *ArbitrageDetector) calculateOptimalTradeSize(market1, market2 *Market) *big.Int {
|
||||
// Use a simple approach: 1% of the smaller liquidity
|
||||
liquidity1 := market1.Liquidity
|
||||
liquidity2 := market2.Liquidity
|
||||
|
||||
minLiquidity := liquidity1
|
||||
if liquidity2.Cmp(liquidity1) < 0 {
|
||||
minLiquidity = liquidity2
|
||||
}
|
||||
|
||||
// Calculate 1% of minimum liquidity
|
||||
optimalSize := new(big.Int).Div(minLiquidity, big.NewInt(100))
|
||||
|
||||
// Ensure minimum trade size (0.001 ETH)
|
||||
minTradeSize := big.NewInt(1000000000000000) // 0.001 ETH in wei
|
||||
if optimalSize.Cmp(minTradeSize) < 0 {
|
||||
optimalSize = minTradeSize
|
||||
}
|
||||
|
||||
// Ensure maximum trade size (10 ETH to avoid overflow)
|
||||
maxTradeSize := new(big.Int).SetInt64(1000000000000000000) // 1 ETH in wei
|
||||
maxTradeSize.Mul(maxTradeSize, big.NewInt(10)) // 10 ETH
|
||||
if optimalSize.Cmp(maxTradeSize) > 0 {
|
||||
optimalSize = maxTradeSize
|
||||
}
|
||||
|
||||
return optimalSize
|
||||
}
|
||||
|
||||
// calculatePriceImpact calculates the price impact for a given trade size
|
||||
func (ad *ArbitrageDetector) calculatePriceImpact(tradeSize *big.Int, market *Market) *big.Float {
|
||||
if market.Liquidity.Sign() == 0 {
|
||||
return big.NewFloat(0.01) // 1% default impact for unknown liquidity
|
||||
}
|
||||
|
||||
// Calculate utilization ratio
|
||||
utilizationRatio := new(big.Float).Quo(new(big.Float).SetInt(tradeSize), new(big.Float).SetInt(market.Liquidity))
|
||||
|
||||
// Apply quadratic model for impact: utilizationRatio * (1 + utilizationRatio)
|
||||
impact := new(big.Float).Mul(utilizationRatio, new(big.Float).Add(big.NewFloat(1), utilizationRatio))
|
||||
|
||||
// Cap impact at 10%
|
||||
if impact.Cmp(big.NewFloat(0.1)) > 0 {
|
||||
impact = big.NewFloat(0.1)
|
||||
}
|
||||
|
||||
return impact
|
||||
}
|
||||
|
||||
// estimateGasCost estimates the gas cost for an arbitrage transaction
|
||||
func (ad *ArbitrageDetector) estimateGasCost(market1, market2 *Market) *big.Int {
|
||||
// Base gas costs for different operations
|
||||
baseGas := big.NewInt(250000) // Base gas for arbitrage transaction
|
||||
|
||||
// Get current gas price (simplified - in production would fetch from network)
|
||||
gasPrice := big.NewInt(2000000000) // 2 gwei base
|
||||
|
||||
// Add priority fee for MEV transactions
|
||||
priorityFee := big.NewInt(5000000000) // 5 gwei priority
|
||||
totalGasPrice := new(big.Int).Add(gasPrice, priorityFee)
|
||||
|
||||
// Calculate total gas cost
|
||||
gasCost := new(big.Int).Mul(baseGas, totalGasPrice)
|
||||
|
||||
return gasCost
|
||||
}
|
||||
|
||||
// GetFeePercentage calculates the total fee percentage for two markets
|
||||
func (ad *ArbitrageDetector) GetFeePercentage(market1, market2 *Market) float64 {
|
||||
fee1 := market1.GetFeePercentage()
|
||||
fee2 := market2.GetFeePercentage()
|
||||
return fee1 + fee2
|
||||
}
|
||||
Reference in New Issue
Block a user