- 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
286 lines
9.2 KiB
Go
286 lines
9.2 KiB
Go
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()
|
|
|
|
// Estimate slippage using simplified AMM formula
|
|
// For constant product AMMs: slippage ≈ trade_size / (2 * liquidity)
|
|
estimatedSlippage := tradeSizeFloat / 2.0
|
|
|
|
// Apply curve adjustment for larger trades (non-linear slippage)
|
|
if tradeSizeFloat > 0.1 { // > 10% of pool
|
|
// Quadratic increase for large trades
|
|
estimatedSlippage = estimatedSlippage * (1 + tradeSizeFloat)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// 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
|
|
}
|