Files
mev-beta/orig/pkg/arbitrum/mev_strategies.go
Administrator 803de231ba 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>
2025-11-10 10:14:26 +01:00

606 lines
20 KiB
Go

package arbitrum
import (
"context"
"fmt"
"math/big"
"sort"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/fraktal/mev-beta/internal/logger"
"github.com/fraktal/mev-beta/pkg/types"
)
// MEVStrategyEngine implements profitable MEV strategies
type MEVStrategyEngine struct {
logger *logger.Logger
protocolRegistry *ArbitrumProtocolRegistry
profitCalculator *RealTimeProfitCalculator
riskManager *RiskManager
// Strategy configuration
minProfitUSD float64 // Minimum profit in USD
maxGasPrice *big.Int // Maximum gas price willing to pay
maxSlippage float64 // Maximum slippage tolerance
// Performance tracking
successfulTrades uint64
totalProfit *big.Int
totalGasCost *big.Int
}
// RealTimeProfitCalculator provides real-time profit calculations
type RealTimeProfitCalculator struct {
// Token prices (token address -> price in USD)
tokenPrices map[common.Address]*TokenPrice
// Gas pricing
currentGasPrice *big.Int
// Exchange rates and fees
exchangeFees map[string]float64 // protocol -> fee percentage
// Liquidity data
poolLiquidity map[common.Address]*PoolLiquidity
}
// RiskManager manages risk parameters for MEV strategies
type RiskManager struct {
maxPositionSize *big.Int // Maximum position size in wei
maxDailyLoss *big.Int // Maximum daily loss
maxConcurrentTxs int // Maximum concurrent transactions
// Current risk metrics
dailyLoss *big.Int
activeTxs int
lastResetTime time.Time
}
// TokenPrice represents real-time token pricing data
type TokenPrice struct {
Address common.Address
PriceUSD float64
LastUpdated time.Time
Confidence float64 // Price confidence 0-1
Volume24h float64
Volatility float64
}
// PoolLiquidity represents pool liquidity information
type PoolLiquidity struct {
Pool common.Address
Token0 common.Address
Token1 common.Address
Reserve0 *big.Int
Reserve1 *big.Int
TotalLiquidity *big.Int
Fee float64
LastUpdated time.Time
}
// ProfitableStrategy represents a profitable MEV strategy
type ProfitableStrategy struct {
Type string // "arbitrage", "sandwich", "liquidation"
Priority int // Higher = more urgent
ExpectedProfit *big.Int // Expected profit in wei
GasCost *big.Int // Estimated gas cost
NetProfit *big.Int // Net profit after gas
ProfitMarginPct float64 // Profit margin percentage
RiskScore float64 // Risk score 0-1 (higher = riskier)
Confidence float64 // Confidence in profit estimate 0-1
ExecutionTime time.Duration // Estimated execution time
Parameters map[string]interface{} // Strategy-specific parameters
}
// ArbitrageParams holds arbitrage-specific parameters
type ArbitrageParams struct {
TokenIn common.Address `json:"token_in"`
TokenOut common.Address `json:"token_out"`
AmountIn *big.Int `json:"amount_in"`
Path []common.Address `json:"path"`
Exchanges []string `json:"exchanges"`
PriceDiff float64 `json:"price_diff"`
Slippage float64 `json:"slippage"`
}
// SandwichParams holds sandwich attack parameters
type SandwichParams struct {
TargetTx string `json:"target_tx"`
TokenIn common.Address `json:"token_in"`
TokenOut common.Address `json:"token_out"`
FrontrunAmount *big.Int `json:"frontrun_amount"`
BackrunAmount *big.Int `json:"backrun_amount"`
Pool common.Address `json:"pool"`
MaxSlippage float64 `json:"max_slippage"`
}
// LiquidationParams holds liquidation-specific parameters
type LiquidationParams struct {
Protocol string `json:"protocol"`
Borrower common.Address `json:"borrower"`
CollateralToken common.Address `json:"collateral_token"`
DebtToken common.Address `json:"debt_token"`
MaxLiquidation *big.Int `json:"max_liquidation"`
HealthFactor float64 `json:"health_factor"`
LiquidationBonus float64 `json:"liquidation_bonus"`
}
// NewMEVStrategyEngine creates a new MEV strategy engine
func NewMEVStrategyEngine(logger *logger.Logger, protocolRegistry *ArbitrumProtocolRegistry) *MEVStrategyEngine {
return &MEVStrategyEngine{
logger: logger,
protocolRegistry: protocolRegistry,
minProfitUSD: 50.0, // $50 minimum profit
maxGasPrice: big.NewInt(20000000000), // 20 gwei max
maxSlippage: 0.005, // 0.5% max slippage
totalProfit: big.NewInt(0),
totalGasCost: big.NewInt(0),
profitCalculator: NewRealTimeProfitCalculator(),
riskManager: NewRiskManager(),
}
}
// NewRealTimeProfitCalculator creates a new profit calculator
func NewRealTimeProfitCalculator() *RealTimeProfitCalculator {
return &RealTimeProfitCalculator{
tokenPrices: make(map[common.Address]*TokenPrice),
exchangeFees: make(map[string]float64),
poolLiquidity: make(map[common.Address]*PoolLiquidity),
currentGasPrice: big.NewInt(5000000000), // 5 gwei default
}
}
// NewRiskManager creates a new risk manager
func NewRiskManager() *RiskManager {
return &RiskManager{
maxPositionSize: big.NewInt(1000000000000000000), // 1 ETH max position
maxDailyLoss: big.NewInt(100000000000000000), // 0.1 ETH max daily loss
maxConcurrentTxs: 5,
dailyLoss: big.NewInt(0),
activeTxs: 0,
lastResetTime: time.Now(),
}
}
// AnalyzeArbitrageOpportunity analyzes potential arbitrage opportunities
func (engine *MEVStrategyEngine) AnalyzeArbitrageOpportunity(ctx context.Context, swapEvent interface{}) (interface{}, error) {
// Type assert the swapEvent to *SwapEvent
swap, ok := swapEvent.(*SwapEvent)
if !ok {
return nil, fmt.Errorf("invalid swap event type")
}
// Parse token addresses
tokenIn := common.HexToAddress(swap.TokenIn)
tokenOut := common.HexToAddress(swap.TokenOut)
// Get current prices across exchanges
prices, err := engine.profitCalculator.GetCrossExchangePrices(ctx, tokenIn, tokenOut)
if err != nil {
return nil, fmt.Errorf("failed to get cross-exchange prices: %w", err)
}
// Find best arbitrage path
bestArb := engine.findBestArbitragePath(prices, tokenIn, tokenOut)
if bestArb == nil {
return nil, nil // No profitable arbitrage
}
// Calculate gas costs
gasCost := engine.calculateArbitrageGasCost(bestArb)
// Calculate net profit
netProfit := new(big.Int).Sub(bestArb.Profit, gasCost)
// Convert to USD for minimum profit check
profitUSD := engine.profitCalculator.WeiToUSD(netProfit, common.HexToAddress("0x82af49447d8a07e3bd95bd0d56f35241523fbab1")) // WETH
if profitUSD < engine.minProfitUSD {
return nil, nil // Below minimum profit threshold
}
// Check risk parameters
riskScore := engine.riskManager.CalculateRiskScore(bestArb)
if riskScore > 0.7 { // Risk too high
return nil, nil
}
return &ProfitableStrategy{
Type: "arbitrage",
Priority: engine.calculatePriority(profitUSD, riskScore),
ExpectedProfit: bestArb.Profit,
GasCost: gasCost,
NetProfit: netProfit,
ProfitMarginPct: (float64(netProfit.Uint64()) / float64(bestArb.AmountIn.Uint64())) * 100,
RiskScore: riskScore,
Confidence: bestArb.Confidence,
ExecutionTime: time.Duration(15) * time.Second, // Estimated execution time
Parameters: map[string]interface{}{
"arbitrage": &ArbitrageParams{
TokenIn: tokenIn,
TokenOut: tokenOut,
AmountIn: bestArb.AmountIn,
Path: []common.Address{bestArb.TokenIn, bestArb.TokenOut},
Exchanges: bestArb.Pools, // Use pools as exchanges
PriceDiff: engine.calculatePriceDifference(prices),
Slippage: engine.maxSlippage,
},
},
}, nil
}
// AnalyzeSandwichOpportunity analyzes potential sandwich attack opportunities
func (engine *MEVStrategyEngine) AnalyzeSandwichOpportunity(ctx context.Context, targetTx *SwapEvent) (*ProfitableStrategy, error) {
// Only analyze large transactions that can be sandwiched profitably
amountIn, ok := new(big.Int).SetString(targetTx.AmountIn, 10)
if !ok || amountIn.Cmp(big.NewInt(100000000000000000)) < 0 { // < 0.1 ETH
return nil, nil
}
// Check if transaction has sufficient slippage tolerance
if targetTx.PriceImpact < 0.01 { // < 1% price impact
return nil, nil
}
tokenIn := common.HexToAddress(targetTx.TokenIn)
tokenOut := common.HexToAddress(targetTx.TokenOut)
// Calculate optimal sandwich amounts
frontrunAmount, backrunAmount := engine.calculateOptimalSandwichAmounts(amountIn, targetTx.PriceImpact)
// Estimate profit from price manipulation
expectedProfit := engine.calculateSandwichProfit(frontrunAmount, backrunAmount, targetTx.PriceImpact)
// Calculate gas costs (frontrun + backrun + priority fees)
gasCost := engine.calculateSandwichGasCost()
// Calculate net profit
netProfit := new(big.Int).Sub(expectedProfit, gasCost)
// Convert to USD
profitUSD := engine.profitCalculator.WeiToUSD(netProfit, tokenOut)
if profitUSD < engine.minProfitUSD {
return nil, nil
}
// Calculate risk (sandwich attacks are inherently risky)
riskScore := 0.6 + (targetTx.PriceImpact * 0.3) // Base risk + impact risk
return &ProfitableStrategy{
Type: "sandwich",
Priority: engine.calculatePriority(profitUSD, riskScore),
ExpectedProfit: expectedProfit,
GasCost: gasCost,
NetProfit: netProfit,
ProfitMarginPct: (float64(netProfit.Uint64()) / float64(amountIn.Uint64())) * 100,
RiskScore: riskScore,
Confidence: 0.7, // Moderate confidence due to MEV competition
ExecutionTime: time.Duration(3) * time.Second, // Fast execution required
Parameters: map[string]interface{}{
"sandwich": &SandwichParams{
TargetTx: targetTx.TxHash,
TokenIn: tokenIn,
TokenOut: tokenOut,
FrontrunAmount: frontrunAmount,
BackrunAmount: backrunAmount,
Pool: common.HexToAddress(targetTx.Pool),
MaxSlippage: engine.maxSlippage,
},
},
}, nil
}
// AnalyzeLiquidationOpportunity analyzes potential liquidation opportunities
func (engine *MEVStrategyEngine) AnalyzeLiquidationOpportunity(ctx context.Context, liquidationEvent *LiquidationEvent) (*ProfitableStrategy, error) {
// Only analyze under-collateralized positions
if liquidationEvent.HealthFactor >= 1.0 {
return nil, nil
}
// Calculate liquidation profitability
collateralAmount, ok := new(big.Int).SetString(liquidationEvent.CollateralAmount, 10)
if !ok {
return nil, fmt.Errorf("invalid collateral amount")
}
debtAmount, ok := new(big.Int).SetString(liquidationEvent.DebtAmount, 10)
if !ok {
return nil, fmt.Errorf("invalid debt amount")
}
// Calculate liquidation bonus (usually 5-15%)
bonusAmount, ok := new(big.Int).SetString(liquidationEvent.Bonus, 10)
if !ok {
bonusAmount = new(big.Int).Div(collateralAmount, big.NewInt(20)) // 5% default
}
// Estimate gas costs for liquidation
gasCost := engine.calculateLiquidationGasCost(liquidationEvent.Protocol)
// Calculate net profit (bonus - gas costs)
netProfit := new(big.Int).Sub(bonusAmount, gasCost)
// Convert to USD
collateralToken := common.HexToAddress(liquidationEvent.CollateralToken)
profitUSD := engine.profitCalculator.WeiToUSD(netProfit, collateralToken)
if profitUSD < engine.minProfitUSD {
return nil, nil
}
// Calculate risk score (liquidations are generally lower risk)
riskScore := 0.3 + (1.0-liquidationEvent.HealthFactor)*0.2
return &ProfitableStrategy{
Type: "liquidation",
Priority: engine.calculatePriority(profitUSD, riskScore),
ExpectedProfit: bonusAmount,
GasCost: gasCost,
NetProfit: netProfit,
ProfitMarginPct: (float64(netProfit.Uint64()) / float64(debtAmount.Uint64())) * 100,
RiskScore: riskScore,
Confidence: 0.9, // High confidence for liquidations
ExecutionTime: time.Duration(10) * time.Second,
Parameters: map[string]interface{}{
"liquidation": &LiquidationParams{
Protocol: liquidationEvent.Protocol,
Borrower: common.HexToAddress(liquidationEvent.Borrower),
CollateralToken: collateralToken,
DebtToken: common.HexToAddress(liquidationEvent.DebtToken),
MaxLiquidation: collateralAmount,
HealthFactor: liquidationEvent.HealthFactor,
LiquidationBonus: float64(bonusAmount.Uint64()) / float64(collateralAmount.Uint64()),
},
},
}, nil
}
// GetCrossExchangePrices gets prices across different exchanges
func (calc *RealTimeProfitCalculator) GetCrossExchangePrices(ctx context.Context, tokenIn, tokenOut common.Address) (map[string]float64, error) {
prices := make(map[string]float64)
// Get prices from major exchanges
exchanges := []string{"uniswap_v3", "uniswap_v2", "sushiswap", "camelot_v3", "balancer_v2"}
for _, exchange := range exchanges {
price, err := calc.getTokenPairPrice(ctx, exchange, tokenIn, tokenOut)
if err != nil {
continue // Skip if price unavailable
}
prices[exchange] = price
}
return prices, nil
}
// Helper methods for calculations
func (engine *MEVStrategyEngine) findBestArbitragePath(prices map[string]float64, tokenIn, tokenOut common.Address) *types.ArbitrageOpportunity {
if len(prices) < 2 {
return nil
}
// Find highest and lowest prices
var minPrice, maxPrice float64
var minExchange, maxExchange string
first := true
for exchange, price := range prices {
if first {
minPrice = price
maxPrice = price
minExchange = exchange
maxExchange = exchange
first = false
continue
}
if price < minPrice {
minPrice = price
minExchange = exchange
}
if price > maxPrice {
maxPrice = price
maxExchange = exchange
}
}
// Calculate potential profit
priceDiff := (maxPrice - minPrice) / minPrice
if priceDiff < 0.005 { // Minimum 0.5% price difference
return nil
}
// Estimate amounts and profit
amountIn := big.NewInt(100000000000000000) // 0.1 ETH test amount
expectedProfit := new(big.Int).Mul(amountIn, big.NewInt(int64(priceDiff*1000)))
expectedProfit = new(big.Int).Div(expectedProfit, big.NewInt(1000))
return &types.ArbitrageOpportunity{
Path: []string{tokenIn.Hex(), tokenOut.Hex()},
Pools: []string{minExchange + "-pool", maxExchange + "-pool"},
AmountIn: amountIn,
Profit: expectedProfit,
NetProfit: expectedProfit, // Simplified - gas will be calculated later
GasEstimate: big.NewInt(200000), // Estimate
ROI: 0.0, // Will be calculated when gas is known
Protocol: "multi-exchange",
ExecutionTime: 4000, // 4 seconds
Confidence: 0.8,
PriceImpact: 0.005, // 0.5% estimated
MaxSlippage: 0.02, // 2% max slippage
TokenIn: tokenIn,
TokenOut: tokenOut,
Timestamp: time.Now().Unix(),
Risk: 0.3, // Medium risk for cross-exchange arbitrage
}
}
func (engine *MEVStrategyEngine) calculateOptimalSandwichAmounts(targetAmount *big.Int, priceImpact float64) (*big.Int, *big.Int) {
// Optimal frontrun is typically 10-30% of target transaction
frontrunPct := 0.15 + (priceImpact * 0.15) // Scale with price impact
frontrunAmount := new(big.Int).Mul(targetAmount, big.NewInt(int64(frontrunPct*100)))
frontrunAmount = new(big.Int).Div(frontrunAmount, big.NewInt(100))
// Backrun amount should be similar to frontrun
backrunAmount := new(big.Int).Set(frontrunAmount)
return frontrunAmount, backrunAmount
}
func (engine *MEVStrategyEngine) calculateSandwichProfit(frontrunAmount, backrunAmount *big.Int, priceImpact float64) *big.Int {
// Simplified calculation: profit = frontrun_amount * (price_impact - fees)
profitPct := priceImpact - 0.006 // Subtract 0.6% for fees and slippage
if profitPct <= 0 {
return big.NewInt(0)
}
profit := new(big.Int).Mul(frontrunAmount, big.NewInt(int64(profitPct*1000)))
profit = new(big.Int).Div(profit, big.NewInt(1000))
return profit
}
// Gas cost calculation methods
func (engine *MEVStrategyEngine) calculateArbitrageGasCost(arb *types.ArbitrageOpportunity) *big.Int {
// Estimate gas usage: swap + transfer operations
gasUsage := big.NewInt(300000) // ~300k gas for complex arbitrage
return new(big.Int).Mul(gasUsage, engine.maxGasPrice)
}
func (engine *MEVStrategyEngine) calculateSandwichGasCost() *big.Int {
// Frontrun + backrun + priority fees
gasUsage := big.NewInt(400000) // ~400k gas total
priorityFee := big.NewInt(10000000000) // 10 gwei priority
totalGasPrice := new(big.Int).Add(engine.maxGasPrice, priorityFee)
return new(big.Int).Mul(gasUsage, totalGasPrice)
}
func (engine *MEVStrategyEngine) calculateLiquidationGasCost(protocol string) *big.Int {
// Different protocols have different gas costs
gasUsage := big.NewInt(200000) // ~200k gas for liquidation
if protocol == "gmx" {
gasUsage = big.NewInt(350000) // GMX is more expensive
}
return new(big.Int).Mul(gasUsage, engine.maxGasPrice)
}
// Utility methods
func (engine *MEVStrategyEngine) calculatePriority(profitUSD, riskScore float64) int {
// Higher profit and lower risk = higher priority
priority := int((profitUSD / 10.0) * (1.0 - riskScore) * 100)
if priority > 1000 {
priority = 1000 // Cap at 1000
}
return priority
}
func (engine *MEVStrategyEngine) calculatePriceDifference(prices map[string]float64) float64 {
if len(prices) < 2 {
return 0
}
var min, max float64
first := true
for _, price := range prices {
if first {
min = price
max = price
first = false
continue
}
if price < min {
min = price
}
if price > max {
max = price
}
}
return (max - min) / min
}
// WeiToUSD converts wei amount to USD using token price
func (calc *RealTimeProfitCalculator) WeiToUSD(amount *big.Int, token common.Address) float64 {
price, exists := calc.tokenPrices[token]
if !exists {
return 0
}
// Convert wei to token units (assume 18 decimals)
tokenAmount := new(big.Float).SetInt(amount)
tokenAmount.Quo(tokenAmount, big.NewFloat(1e18))
tokenAmountFloat, _ := tokenAmount.Float64()
return tokenAmountFloat * price.PriceUSD
}
func (calc *RealTimeProfitCalculator) getTokenPairPrice(ctx context.Context, exchange string, tokenIn, tokenOut common.Address) (float64, error) {
// This would connect to actual DEX contracts or price oracles
// For now, we'll return an error to indicate this needs implementation
return 0, fmt.Errorf("getTokenPairPrice not implemented - needs connection to actual DEX contracts or price oracles")
}
// CalculateRiskScore calculates risk score for a strategy
func (rm *RiskManager) CalculateRiskScore(arb *types.ArbitrageOpportunity) float64 {
// Base risk factors
baseRisk := 0.1
// Size risk - larger positions are riskier
sizeRisk := float64(arb.AmountIn.Uint64()) / 1e18 * 0.1 // 0.1 per ETH
// Confidence risk
confidenceRisk := (1.0 - arb.Confidence) * 0.3
// Path complexity risk
pathRisk := float64(len(arb.Path)-2) * 0.05 // Additional risk for each hop
totalRisk := baseRisk + sizeRisk + confidenceRisk + pathRisk
// Cap at 1.0
if totalRisk > 1.0 {
totalRisk = 1.0
}
return totalRisk
}
// GetTopStrategies returns the most profitable strategies sorted by priority
func (engine *MEVStrategyEngine) GetTopStrategies(strategies []*ProfitableStrategy, limit int) []*ProfitableStrategy {
// Sort by priority (highest first)
sort.Slice(strategies, func(i, j int) bool {
return strategies[i].Priority > strategies[j].Priority
})
// Apply limit
if len(strategies) > limit {
strategies = strategies[:limit]
}
return strategies
}
// UpdatePerformanceMetrics updates strategy performance tracking
func (engine *MEVStrategyEngine) UpdatePerformanceMetrics(strategy *ProfitableStrategy, actualProfit *big.Int, gasCost *big.Int) {
if actualProfit.Sign() > 0 {
engine.successfulTrades++
engine.totalProfit.Add(engine.totalProfit, actualProfit)
}
engine.totalGasCost.Add(engine.totalGasCost, gasCost)
}
// GetPerformanceStats returns performance statistics
func (engine *MEVStrategyEngine) GetPerformanceStats() map[string]interface{} {
netProfit := new(big.Int).Sub(engine.totalProfit, engine.totalGasCost)
return map[string]interface{}{
"successful_trades": engine.successfulTrades,
"total_profit_wei": engine.totalProfit.String(),
"total_gas_cost": engine.totalGasCost.String(),
"net_profit_wei": netProfit.String(),
"profit_ratio": float64(engine.totalProfit.Uint64()) / float64(engine.totalGasCost.Uint64()),
}
}