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:
348
orig/pkg/profitcalc/slippage_protection.go
Normal file
348
orig/pkg/profitcalc/slippage_protection.go
Normal file
@@ -0,0 +1,348 @@
|
||||
package profitcalc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/fraktal/mev-beta/internal/logger"
|
||||
)
|
||||
|
||||
// SlippageProtector provides slippage calculation and protection for arbitrage trades
|
||||
type SlippageProtector struct {
|
||||
logger *logger.Logger
|
||||
maxSlippageBps int64 // Maximum allowed slippage in basis points
|
||||
liquidityBuffer float64 // Buffer factor for liquidity calculations
|
||||
}
|
||||
|
||||
// SlippageAnalysis contains detailed slippage analysis for a trade
|
||||
type SlippageAnalysis struct {
|
||||
TradeAmount *big.Float // Amount being traded
|
||||
PoolLiquidity *big.Float // Available liquidity in pool
|
||||
EstimatedSlippage float64 // Estimated slippage percentage
|
||||
SlippageBps int64 // Slippage in basis points
|
||||
PriceImpact float64 // Price impact percentage
|
||||
EffectivePrice *big.Float // Price after slippage
|
||||
MinAmountOut *big.Float // Minimum amount out with slippage protection
|
||||
IsAcceptable bool // Whether slippage is within acceptable limits
|
||||
RiskLevel string // "Low", "Medium", "High", "Extreme"
|
||||
Recommendation string // Trading recommendation
|
||||
}
|
||||
|
||||
// NewSlippageProtector creates a new slippage protection manager
|
||||
func NewSlippageProtector(logger *logger.Logger) *SlippageProtector {
|
||||
return &SlippageProtector{
|
||||
logger: logger,
|
||||
maxSlippageBps: 500, // 5% maximum slippage
|
||||
liquidityBuffer: 0.8, // Use 80% of available liquidity for calculations
|
||||
}
|
||||
}
|
||||
|
||||
// AnalyzeSlippage performs comprehensive slippage analysis for a potential trade
|
||||
func (sp *SlippageProtector) AnalyzeSlippage(
|
||||
tradeAmount *big.Float,
|
||||
poolLiquidity *big.Float,
|
||||
currentPrice *big.Float,
|
||||
) *SlippageAnalysis {
|
||||
|
||||
if tradeAmount == nil || poolLiquidity == nil || currentPrice == nil {
|
||||
return &SlippageAnalysis{
|
||||
IsAcceptable: false,
|
||||
RiskLevel: "Extreme",
|
||||
Recommendation: "Insufficient data for slippage calculation",
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate trade size as percentage of pool liquidity
|
||||
tradeSizeRatio := new(big.Float).Quo(tradeAmount, poolLiquidity)
|
||||
tradeSizeFloat, _ := tradeSizeRatio.Float64()
|
||||
|
||||
// CRITICAL FIX: Use proper constant product AMM formula
|
||||
// Bug: Old formula "tradeSize / 2.0" is mathematically incorrect
|
||||
// Correct Uniswap V2 formula: dy = (y * dx * 997) / (x * 1000 + dx * 997)
|
||||
// Slippage = (marketPrice - executionPrice) / marketPrice
|
||||
estimatedSlippage := sp.calculateConstantProductSlippage(tradeAmount, poolLiquidity, currentPrice)
|
||||
|
||||
slippageBps := int64(estimatedSlippage * 10000)
|
||||
|
||||
// Calculate price impact (similar to slippage but different calculation)
|
||||
priceImpact := sp.calculatePriceImpact(tradeSizeFloat)
|
||||
|
||||
// Calculate effective price after slippage
|
||||
slippageFactor := 1.0 - estimatedSlippage
|
||||
effectivePrice := new(big.Float).Mul(currentPrice, big.NewFloat(slippageFactor))
|
||||
|
||||
// Calculate minimum amount out with slippage protection
|
||||
minAmountOut := new(big.Float).Quo(tradeAmount, effectivePrice)
|
||||
|
||||
// Determine risk level and acceptability
|
||||
riskLevel, isAcceptable := sp.assessRiskLevel(slippageBps, tradeSizeFloat)
|
||||
recommendation := sp.generateRecommendation(slippageBps, tradeSizeFloat, riskLevel)
|
||||
|
||||
analysis := &SlippageAnalysis{
|
||||
TradeAmount: tradeAmount,
|
||||
PoolLiquidity: poolLiquidity,
|
||||
EstimatedSlippage: estimatedSlippage,
|
||||
SlippageBps: slippageBps,
|
||||
PriceImpact: priceImpact,
|
||||
EffectivePrice: effectivePrice,
|
||||
MinAmountOut: minAmountOut,
|
||||
IsAcceptable: isAcceptable,
|
||||
RiskLevel: riskLevel,
|
||||
Recommendation: recommendation,
|
||||
}
|
||||
|
||||
sp.logger.Debug(fmt.Sprintf("Slippage analysis: Trade=%s, Liquidity=%s, Slippage=%.2f%%, Risk=%s",
|
||||
tradeAmount.String(), poolLiquidity.String(), estimatedSlippage*100, riskLevel))
|
||||
|
||||
return analysis
|
||||
}
|
||||
|
||||
// calculateConstantProductSlippage calculates slippage using correct Uniswap V2 constant product formula
|
||||
// Formula: dy = (y * dx * 997) / (x * 1000 + dx * 997)
|
||||
// Where: x = reserveIn, y = reserveOut, dx = amountIn, dy = amountOut
|
||||
// Slippage = (marketPrice - executionPrice) / marketPrice
|
||||
func (sp *SlippageProtector) calculateConstantProductSlippage(
|
||||
amountIn *big.Float,
|
||||
reserveIn *big.Float,
|
||||
marketPrice *big.Float,
|
||||
) float64 {
|
||||
// Handle edge cases
|
||||
if amountIn == nil || reserveIn == nil || marketPrice == nil {
|
||||
return 1.0 // 100% slippage for invalid inputs
|
||||
}
|
||||
if amountIn.Sign() <= 0 || reserveIn.Sign() <= 0 || marketPrice.Sign() <= 0 {
|
||||
return 1.0
|
||||
}
|
||||
|
||||
// Calculate: dx * 997 (Uniswap V2 fee is 0.3%, so 997/1000 remains)
|
||||
fee := big.NewFloat(997)
|
||||
dx997 := new(big.Float).Mul(amountIn, fee)
|
||||
|
||||
// Calculate: x * 1000
|
||||
x1000 := new(big.Float).Mul(reserveIn, big.NewFloat(1000))
|
||||
|
||||
// Calculate: x * 1000 + dx * 997
|
||||
denominator := new(big.Float).Add(x1000, dx997)
|
||||
|
||||
// Calculate execution price = dx / (x * 1000 + dx * 997)
|
||||
// This represents how much the price moved
|
||||
executionRatio := new(big.Float).Quo(dx997, denominator)
|
||||
|
||||
// For small trades, slippage ≈ executionRatio / 2
|
||||
// For larger trades, use more precise calculation
|
||||
tradeSizeRatio := new(big.Float).Quo(amountIn, reserveIn)
|
||||
tradeSizeFloat, _ := tradeSizeRatio.Float64()
|
||||
|
||||
var slippage float64
|
||||
if tradeSizeFloat < 0.01 { // < 1% of pool
|
||||
// Linear approximation for small trades
|
||||
ratioFloat, _ := executionRatio.Float64()
|
||||
slippage = ratioFloat / 2.0
|
||||
} else {
|
||||
// Full calculation for larger trades
|
||||
// Slippage = (marketPrice - executionPrice) / marketPrice
|
||||
// executionPrice = dy / dx = (y * 997) / (x * 1000 + dx * 997)
|
||||
// Simplified: slippage ≈ dx / (2 * (x + dx/2))
|
||||
|
||||
halfDx := new(big.Float).Quo(amountIn, big.NewFloat(2))
|
||||
reservePlusHalfDx := new(big.Float).Add(reserveIn, halfDx)
|
||||
twoTimesReservePlusHalfDx := new(big.Float).Mul(reservePlusHalfDx, big.NewFloat(2))
|
||||
slippageFloat := new(big.Float).Quo(amountIn, twoTimesReservePlusHalfDx)
|
||||
slippage, _ = slippageFloat.Float64()
|
||||
|
||||
// Apply fee adjustment (0.3% fee adds to effective slippage)
|
||||
slippage += 0.003
|
||||
}
|
||||
|
||||
// Cap at 100%
|
||||
if slippage > 1.0 {
|
||||
slippage = 1.0
|
||||
} else if slippage < 0 {
|
||||
slippage = 0
|
||||
}
|
||||
|
||||
return slippage
|
||||
}
|
||||
|
||||
// calculatePriceImpact calculates price impact using AMM mechanics
|
||||
func (sp *SlippageProtector) calculatePriceImpact(tradeSizeRatio float64) float64 {
|
||||
// For constant product AMMs (like Uniswap V2):
|
||||
// Price impact = trade_size / (1 + trade_size)
|
||||
priceImpact := tradeSizeRatio / (1.0 + tradeSizeRatio)
|
||||
|
||||
// Cap at 100%
|
||||
if priceImpact > 1.0 {
|
||||
priceImpact = 1.0
|
||||
}
|
||||
|
||||
return priceImpact
|
||||
}
|
||||
|
||||
// assessRiskLevel determines risk level based on slippage and trade size
|
||||
func (sp *SlippageProtector) assessRiskLevel(slippageBps int64, tradeSizeRatio float64) (string, bool) {
|
||||
isAcceptable := slippageBps <= sp.maxSlippageBps
|
||||
|
||||
var riskLevel string
|
||||
switch {
|
||||
case slippageBps <= 50: // <= 0.5%
|
||||
riskLevel = "Low"
|
||||
case slippageBps <= 200: // <= 2%
|
||||
riskLevel = "Medium"
|
||||
case slippageBps <= 500: // <= 5%
|
||||
riskLevel = "High"
|
||||
default:
|
||||
riskLevel = "Extreme"
|
||||
isAcceptable = false
|
||||
}
|
||||
|
||||
// Additional checks for trade size
|
||||
if tradeSizeRatio > 0.5 { // > 50% of pool
|
||||
riskLevel = "Extreme"
|
||||
isAcceptable = false
|
||||
} else if tradeSizeRatio > 0.2 { // > 20% of pool
|
||||
if riskLevel == "Low" {
|
||||
riskLevel = "Medium"
|
||||
} else if riskLevel == "Medium" {
|
||||
riskLevel = "High"
|
||||
}
|
||||
}
|
||||
|
||||
return riskLevel, isAcceptable
|
||||
}
|
||||
|
||||
// generateRecommendation provides trading recommendations based on analysis
|
||||
func (sp *SlippageProtector) generateRecommendation(slippageBps int64, tradeSizeRatio float64, riskLevel string) string {
|
||||
switch riskLevel {
|
||||
case "Low":
|
||||
return "Safe to execute - low slippage expected"
|
||||
case "Medium":
|
||||
if tradeSizeRatio > 0.1 {
|
||||
return "Consider splitting trade into smaller parts"
|
||||
}
|
||||
return "Proceed with caution - moderate slippage expected"
|
||||
case "High":
|
||||
return "High slippage risk - consider reducing trade size or finding alternative routes"
|
||||
case "Extreme":
|
||||
if tradeSizeRatio > 0.5 {
|
||||
return "Trade too large for pool - split into multiple smaller trades"
|
||||
}
|
||||
return "Excessive slippage - avoid this trade"
|
||||
default:
|
||||
return "Unable to assess - insufficient data"
|
||||
}
|
||||
}
|
||||
|
||||
// CalculateOptimalTradeSize calculates optimal trade size to stay within slippage limits
|
||||
func (sp *SlippageProtector) CalculateOptimalTradeSize(
|
||||
poolLiquidity *big.Float,
|
||||
maxSlippageBps int64,
|
||||
) *big.Float {
|
||||
|
||||
if poolLiquidity == nil || poolLiquidity.Sign() <= 0 {
|
||||
return big.NewFloat(0)
|
||||
}
|
||||
|
||||
// Convert max slippage to ratio
|
||||
maxSlippageRatio := float64(maxSlippageBps) / 10000.0
|
||||
|
||||
// For simplified AMM: optimal_trade_size = 2 * liquidity * max_slippage
|
||||
optimalRatio := 2.0 * maxSlippageRatio
|
||||
|
||||
// Apply safety factor
|
||||
safetyFactor := 0.8 // Use 80% of optimal to be conservative
|
||||
optimalRatio *= safetyFactor
|
||||
|
||||
optimalSize := new(big.Float).Mul(poolLiquidity, big.NewFloat(optimalRatio))
|
||||
|
||||
sp.logger.Debug(fmt.Sprintf("Calculated optimal trade size: %s (%.2f%% of pool) for max slippage %d bps",
|
||||
optimalSize.String(), optimalRatio*100, maxSlippageBps))
|
||||
|
||||
return optimalSize
|
||||
}
|
||||
|
||||
// EstimateGasForSlippage estimates additional gas needed for slippage protection
|
||||
func (sp *SlippageProtector) EstimateGasForSlippage(analysis *SlippageAnalysis) uint64 {
|
||||
baseGas := uint64(0)
|
||||
|
||||
// Higher slippage might require more complex routing
|
||||
switch analysis.RiskLevel {
|
||||
case "Low":
|
||||
baseGas = 0 // No additional gas
|
||||
case "Medium":
|
||||
baseGas = 20000 // Additional gas for price checks
|
||||
case "High":
|
||||
baseGas = 50000 // Additional gas for complex routing
|
||||
case "Extreme":
|
||||
baseGas = 100000 // Maximum additional gas for emergency handling
|
||||
}
|
||||
|
||||
return baseGas
|
||||
}
|
||||
|
||||
// SetMaxSlippage updates the maximum allowed slippage
|
||||
func (sp *SlippageProtector) SetMaxSlippage(bps int64) {
|
||||
sp.maxSlippageBps = bps
|
||||
sp.logger.Info(fmt.Sprintf("Updated maximum slippage to %d bps (%.2f%%)", bps, float64(bps)/100))
|
||||
}
|
||||
|
||||
// GetMaxSlippage returns the current maximum slippage setting
|
||||
func (sp *SlippageProtector) GetMaxSlippage() int64 {
|
||||
return sp.maxSlippageBps
|
||||
}
|
||||
|
||||
// ValidateTradeParameters performs comprehensive validation of trade parameters
|
||||
func (sp *SlippageProtector) ValidateTradeParameters(
|
||||
tradeAmount *big.Float,
|
||||
poolLiquidity *big.Float,
|
||||
minLiquidity *big.Float,
|
||||
) error {
|
||||
|
||||
if tradeAmount == nil || tradeAmount.Sign() <= 0 {
|
||||
return fmt.Errorf("invalid trade amount: must be positive")
|
||||
}
|
||||
|
||||
if poolLiquidity == nil || poolLiquidity.Sign() <= 0 {
|
||||
return fmt.Errorf("invalid pool liquidity: must be positive")
|
||||
}
|
||||
|
||||
if minLiquidity != nil && poolLiquidity.Cmp(minLiquidity) < 0 {
|
||||
return fmt.Errorf("insufficient pool liquidity: %s < %s required",
|
||||
poolLiquidity.String(), minLiquidity.String())
|
||||
}
|
||||
|
||||
// Check if trade is reasonable relative to pool size
|
||||
tradeSizeRatio := new(big.Float).Quo(tradeAmount, poolLiquidity)
|
||||
tradeSizeFloat, _ := tradeSizeRatio.Float64()
|
||||
|
||||
if tradeSizeFloat > 0.9 { // > 90% of pool
|
||||
return fmt.Errorf("trade size too large: %.1f%% of pool liquidity", tradeSizeFloat*100)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CalculateSlippageAdjustedProfit adjusts profit calculations for slippage
|
||||
func (sp *SlippageProtector) CalculateSlippageAdjustedProfit(
|
||||
grossProfit *big.Float,
|
||||
analysis *SlippageAnalysis,
|
||||
) *big.Float {
|
||||
|
||||
if grossProfit == nil || analysis == nil {
|
||||
return big.NewFloat(0)
|
||||
}
|
||||
|
||||
// Reduce profit by estimated slippage impact
|
||||
slippageImpact := big.NewFloat(analysis.EstimatedSlippage)
|
||||
slippageReduction := new(big.Float).Mul(grossProfit, slippageImpact)
|
||||
adjustedProfit := new(big.Float).Sub(grossProfit, slippageReduction)
|
||||
|
||||
// Ensure profit doesn't go negative due to slippage
|
||||
if adjustedProfit.Sign() < 0 {
|
||||
adjustedProfit = big.NewFloat(0)
|
||||
}
|
||||
|
||||
sp.logger.Debug(fmt.Sprintf("Slippage-adjusted profit: %s -> %s (reduction: %s)",
|
||||
grossProfit.String(), adjustedProfit.String(), slippageReduction.String()))
|
||||
|
||||
return adjustedProfit
|
||||
}
|
||||
Reference in New Issue
Block a user