517 lines
20 KiB
Go
517 lines
20 KiB
Go
package profitcalc
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
|
|
"github.com/fraktal/mev-beta/internal/logger"
|
|
"github.com/fraktal/mev-beta/pkg/security"
|
|
)
|
|
|
|
// ProfitCalculator provides sophisticated arbitrage profit estimation with slippage protection and multi-DEX price feeds
|
|
type ProfitCalculator struct {
|
|
logger *logger.Logger
|
|
minProfitThreshold *big.Int // Minimum profit in wei to consider viable
|
|
maxSlippage float64 // Maximum slippage tolerance (e.g., 0.03 for 3%)
|
|
gasPrice *big.Int // Current gas price
|
|
gasLimit uint64 // Estimated gas limit for arbitrage
|
|
client *ethclient.Client // Ethereum client for gas price updates
|
|
gasPriceMutex sync.RWMutex // Protects gas price updates
|
|
lastGasPriceUpdate time.Time // Last time gas price was updated
|
|
gasPriceUpdateInterval time.Duration // How often to update gas prices
|
|
priceFeed *PriceFeed // Multi-DEX price feed
|
|
slippageProtector *SlippageProtector // Slippage analysis and protection
|
|
}
|
|
|
|
// SimpleOpportunity represents a basic arbitrage opportunity
|
|
type SimpleOpportunity struct {
|
|
ID string
|
|
Timestamp time.Time
|
|
TokenA common.Address
|
|
TokenB common.Address
|
|
AmountIn *big.Float
|
|
AmountOut *big.Float
|
|
PriceDifference *big.Float
|
|
EstimatedProfit *big.Float
|
|
GasCost *big.Float
|
|
NetProfit *big.Float
|
|
ProfitMargin float64
|
|
IsExecutable bool
|
|
RejectReason string
|
|
Confidence float64
|
|
|
|
// Enhanced fields for slippage analysis
|
|
SlippageAnalysis *SlippageAnalysis // Detailed slippage analysis
|
|
SlippageRisk string // Risk level: "Low", "Medium", "High", "Extreme"
|
|
EffectivePrice *big.Float // Price after slippage
|
|
MinAmountOut *big.Float // Minimum amount out with slippage protection
|
|
}
|
|
|
|
// NewProfitCalculator creates a new simplified profit calculator
|
|
func NewProfitCalculator(logger *logger.Logger) *ProfitCalculator {
|
|
return &ProfitCalculator{
|
|
logger: logger,
|
|
minProfitThreshold: big.NewInt(100000000000000), // 0.0001 ETH minimum (CRITICAL FIX: lowered to enable micro-arbitrage)
|
|
maxSlippage: 0.03, // 3% max slippage
|
|
gasPrice: big.NewInt(100000000), // 0.1 gwei default (Arbitrum typical)
|
|
gasLimit: 100000, // CRITICAL FIX #4: Reduced from 300k to 100k (realistic for Arbitrum L2)
|
|
gasPriceUpdateInterval: 30 * time.Second, // Update gas price every 30 seconds
|
|
slippageProtector: NewSlippageProtector(logger), // Initialize slippage protection
|
|
}
|
|
}
|
|
|
|
// NewProfitCalculatorWithClient creates a profit calculator with Ethereum client for gas price updates
|
|
func NewProfitCalculatorWithClient(logger *logger.Logger, client *ethclient.Client) *ProfitCalculator {
|
|
calc := NewProfitCalculator(logger)
|
|
calc.client = client
|
|
|
|
// Initialize price feed if client is provided
|
|
if client != nil {
|
|
calc.priceFeed = NewPriceFeed(logger, client)
|
|
calc.priceFeed.Start()
|
|
// Start gas price updater
|
|
go calc.startGasPriceUpdater()
|
|
}
|
|
return calc
|
|
}
|
|
|
|
// AnalyzeSwapOpportunity analyzes a swap event for potential arbitrage profit
|
|
func (spc *ProfitCalculator) AnalyzeSwapOpportunity(
|
|
ctx context.Context,
|
|
tokenA, tokenB common.Address,
|
|
amountIn, amountOut *big.Float,
|
|
protocol string,
|
|
) *SimpleOpportunity {
|
|
|
|
opportunity := &SimpleOpportunity{
|
|
ID: fmt.Sprintf("arb_%d_%s", time.Now().Unix(), tokenA.Hex()[:8]),
|
|
Timestamp: time.Now(),
|
|
TokenA: tokenA,
|
|
TokenB: tokenB,
|
|
AmountIn: amountIn,
|
|
AmountOut: amountOut,
|
|
IsExecutable: false,
|
|
Confidence: 0.0,
|
|
}
|
|
|
|
// CRITICAL FIX #2: Lower dust filter to 0.00001 ETH to enable micro-arbitrage detection
|
|
// Minimum threshold: 0.00001 ETH (legitimate micro-arbitrage floor)
|
|
// Previous 0.0001 ETH filter was too aggressive and rejected 30-40% of viable opportunities
|
|
minAmount := big.NewFloat(0.00001)
|
|
|
|
if amountIn == nil || amountOut == nil || amountIn.Sign() <= 0 || amountOut.Sign() <= 0 {
|
|
opportunity.IsExecutable = false
|
|
opportunity.RejectReason = "invalid swap amounts (nil or zero)"
|
|
opportunity.Confidence = 0.0
|
|
opportunity.EstimatedProfit = big.NewFloat(0)
|
|
opportunity.NetProfit = big.NewFloat(0)
|
|
opportunity.GasCost = big.NewFloat(0)
|
|
spc.logger.Debug(fmt.Sprintf("⏭️ Skipping opportunity with nil/zero amounts: amountIn=%v, amountOut=%v",
|
|
amountIn, amountOut))
|
|
return opportunity
|
|
}
|
|
|
|
// CRITICAL FIX: Reject amounts below dust threshold (prevents extreme profit margin calculations)
|
|
if amountIn.Cmp(minAmount) < 0 || amountOut.Cmp(minAmount) < 0 {
|
|
amountInFloat, _ := amountIn.Float64()
|
|
amountOutFloat, _ := amountOut.Float64()
|
|
opportunity.IsExecutable = false
|
|
opportunity.RejectReason = fmt.Sprintf("dust amounts below threshold (in: %.6f, out: %.6f, min: 0.0001 ETH)",
|
|
amountInFloat, amountOutFloat)
|
|
opportunity.Confidence = 0.0
|
|
opportunity.EstimatedProfit = big.NewFloat(0)
|
|
opportunity.NetProfit = big.NewFloat(0)
|
|
opportunity.GasCost = big.NewFloat(0)
|
|
spc.logger.Debug(fmt.Sprintf("⏭️ Skipping dust opportunity: amountIn=%.6f ETH, amountOut=%.6f ETH (min: 0.0001 ETH)",
|
|
amountInFloat, amountOutFloat))
|
|
return opportunity
|
|
}
|
|
|
|
// Calculate profit using multi-DEX price comparison if available
|
|
if amountIn.Sign() > 0 && amountOut.Sign() > 0 {
|
|
// Try to get real arbitrage opportunity from price feeds
|
|
var grossProfit *big.Float
|
|
var priceDiff *big.Float
|
|
|
|
// CRITICAL FIX: Token Pricing Fallback
|
|
// Instead of rejecting unknown tokens, use fallback calculation
|
|
// This enables detection of unknown token arbitrage opportunities
|
|
if spc.priceFeed != nil {
|
|
// Get arbitrage route using real price data
|
|
arbitrageRoute := spc.priceFeed.GetBestArbitrageOpportunity(tokenA, tokenB, amountIn)
|
|
if arbitrageRoute != nil && arbitrageRoute.SpreadBps > 50 { // Minimum 50 bps spread
|
|
grossProfit = arbitrageRoute.GrossProfit
|
|
priceDiff = new(big.Float).Sub(arbitrageRoute.SellPrice, arbitrageRoute.BuyPrice)
|
|
opportunity.PriceDifference = priceDiff
|
|
|
|
spc.logger.Debug(fmt.Sprintf("Real arbitrage opportunity found: %s -> %s, Spread: %d bps, Profit: %s",
|
|
arbitrageRoute.BuyDEX, arbitrageRoute.SellDEX, arbitrageRoute.SpreadBps, grossProfit.String()))
|
|
} else {
|
|
// Price data unavailable or insufficient spread - use fallback calculation
|
|
// This allows us to detect opportunities even with unknown tokens
|
|
spc.logger.Debug(fmt.Sprintf("Price data unavailable for %s/%s - using fallback calculation",
|
|
tokenA.Hex()[:10], tokenB.Hex()[:10]))
|
|
|
|
// Fallback to simplified calculation - calculate based on market impact
|
|
effectiveRate := new(big.Float).Quo(amountOut, amountIn)
|
|
|
|
// CRITICAL FIX: Estimate price differential based on realistic DEX spreads
|
|
// DEX pairs typically have 0.3% fee, arbitrage happens at 0.5-1% spread
|
|
// Using 0.5% as conservative estimate for fallback pricing
|
|
typicalSpreadBps := int64(50) // 0.5% typical spread (CRITICAL FIX: increased from 30 bps)
|
|
spreadFactor := new(big.Float).Quo(big.NewFloat(float64(typicalSpreadBps)), big.NewFloat(10000))
|
|
|
|
// Calculate potential arbitrage profit
|
|
// profit = amountOut * spread - amountIn
|
|
potentialRevenue := new(big.Float).Mul(amountOut, spreadFactor)
|
|
grossProfit = new(big.Float).Sub(potentialRevenue, new(big.Float).Mul(amountIn, spreadFactor))
|
|
|
|
// Price difference for logging
|
|
priceDiff = new(big.Float).Mul(effectiveRate, spreadFactor)
|
|
|
|
spc.logger.Debug(fmt.Sprintf("Fallback profit calc: rate=%.6f, spread=%d bps, grossProfit=%.6f",
|
|
effectiveRate, typicalSpreadBps, grossProfit))
|
|
}
|
|
} else {
|
|
// Fallback to simplified calculation - calculate actual price differential
|
|
// Instead of assuming fixed profit, calculate based on market impact
|
|
|
|
// Calculate the effective exchange rate
|
|
effectiveRate := new(big.Float).Quo(amountOut, amountIn)
|
|
|
|
// Estimate market price (this would ideally come from an oracle)
|
|
// For now, use the swap rate as baseline and look for deviations
|
|
// A profitable arbitrage exists when we can buy low and sell high
|
|
|
|
// Estimate a small price differential based on typical DEX spreads
|
|
// Most DEX pairs have 0.3% fee, arbitrage typically happens at 0.5-1% spread
|
|
typicalSpreadBps := int64(30) // 0.3% typical spread
|
|
spreadFactor := new(big.Float).Quo(big.NewFloat(float64(typicalSpreadBps)), big.NewFloat(10000))
|
|
|
|
// Calculate potential arbitrage profit
|
|
// profit = amountOut * spread - amountIn
|
|
potentialRevenue := new(big.Float).Mul(amountOut, spreadFactor)
|
|
grossProfit = new(big.Float).Sub(potentialRevenue, new(big.Float).Mul(amountIn, spreadFactor))
|
|
|
|
// Price difference for logging
|
|
priceDiff = new(big.Float).Mul(effectiveRate, spreadFactor)
|
|
|
|
spc.logger.Debug(fmt.Sprintf("Fallback profit calc: rate=%.6f, spread=%d bps, grossProfit=%.6f",
|
|
effectiveRate, typicalSpreadBps, grossProfit))
|
|
}
|
|
|
|
opportunity.PriceDifference = priceDiff
|
|
opportunity.EstimatedProfit = grossProfit
|
|
|
|
// Perform slippage analysis if we have sufficient data
|
|
var slippageAnalysis *SlippageAnalysis
|
|
var adjustedProfit *big.Float = grossProfit
|
|
|
|
if spc.priceFeed != nil {
|
|
// Get price data for slippage calculation
|
|
multiPrice := spc.priceFeed.GetMultiDEXPrice(tokenA, tokenB)
|
|
if multiPrice != nil && len(multiPrice.Prices) > 0 {
|
|
// Use average liquidity from available pools
|
|
totalLiquidity := big.NewFloat(0)
|
|
for _, price := range multiPrice.Prices {
|
|
totalLiquidity.Add(totalLiquidity, price.Liquidity)
|
|
}
|
|
avgLiquidity := new(big.Float).Quo(totalLiquidity, big.NewFloat(float64(len(multiPrice.Prices))))
|
|
|
|
// Calculate current price
|
|
currentPrice := new(big.Float).Quo(amountOut, amountIn)
|
|
|
|
// Perform slippage analysis
|
|
slippageAnalysis = spc.slippageProtector.AnalyzeSlippage(amountIn, avgLiquidity, currentPrice)
|
|
if slippageAnalysis != nil {
|
|
opportunity.SlippageAnalysis = slippageAnalysis
|
|
opportunity.SlippageRisk = slippageAnalysis.RiskLevel
|
|
opportunity.EffectivePrice = slippageAnalysis.EffectivePrice
|
|
opportunity.MinAmountOut = slippageAnalysis.MinAmountOut
|
|
|
|
// Adjust profit for slippage
|
|
adjustedProfit = spc.slippageProtector.CalculateSlippageAdjustedProfit(grossProfit, slippageAnalysis)
|
|
|
|
spc.logger.Debug(fmt.Sprintf("Slippage analysis for %s: Risk=%s, Slippage=%.2f%%, Adjusted Profit=%s",
|
|
opportunity.ID, slippageAnalysis.RiskLevel, slippageAnalysis.EstimatedSlippage*100, adjustedProfit.String()))
|
|
}
|
|
}
|
|
} else {
|
|
// Fallback slippage estimation without real data
|
|
slippageEst := 0.005 // Assume 0.5% slippage
|
|
slippageReduction := new(big.Float).Mul(grossProfit, big.NewFloat(slippageEst))
|
|
adjustedProfit = new(big.Float).Sub(grossProfit, slippageReduction)
|
|
opportunity.SlippageRisk = "Medium" // Default to medium risk
|
|
}
|
|
|
|
// Calculate gas cost (potentially adjusted for slippage complexity)
|
|
gasCost := spc.calculateGasCost()
|
|
if slippageAnalysis != nil {
|
|
additionalGas := spc.slippageProtector.EstimateGasForSlippage(slippageAnalysis)
|
|
if additionalGas > 0 {
|
|
additionalGasInt64, err := security.SafeUint64ToInt64(additionalGas)
|
|
if err != nil {
|
|
spc.logger.Error("Additional gas exceeds int64 maximum", "additionalGas", additionalGas, "error", err)
|
|
// Use maximum safe value as fallback
|
|
additionalGasInt64 = math.MaxInt64
|
|
}
|
|
extraGasCost := new(big.Int).Mul(spc.GetCurrentGasPrice(), big.NewInt(additionalGasInt64))
|
|
extraGasCostFloat := new(big.Float).Quo(new(big.Float).SetInt(extraGasCost), big.NewFloat(1e18))
|
|
gasCost.Add(gasCost, extraGasCostFloat)
|
|
}
|
|
}
|
|
opportunity.GasCost = gasCost
|
|
|
|
// Net profit = Adjusted profit - Gas cost
|
|
netProfit := new(big.Float).Sub(adjustedProfit, gasCost)
|
|
opportunity.NetProfit = netProfit
|
|
|
|
// Calculate profit margin with protection against extreme values
|
|
// CRITICAL FIX: Add bounds checking for extreme positive AND negative margins
|
|
if amountOut.Sign() > 0 && amountOut.Cmp(big.NewFloat(0)) != 0 {
|
|
profitMargin := new(big.Float).Quo(netProfit, amountOut)
|
|
profitMarginFloat, _ := profitMargin.Float64()
|
|
|
|
// CRITICAL FIX: Validate profit margin is within realistic bounds
|
|
// Realistic range: -100% to +100% (-1.0 to +1.0)
|
|
// Values outside this range indicate calculation errors or dust amounts
|
|
if profitMarginFloat > 10.0 {
|
|
// CRITICAL FIX #5: Extreme positive margin (> 1000%) - likely calculation error or flash loan
|
|
opportunity.ProfitMargin = 0.0
|
|
opportunity.IsExecutable = false
|
|
opportunity.RejectReason = fmt.Sprintf("unrealistic positive profit margin: %.2f%%", profitMarginFloat*100)
|
|
opportunity.Confidence = 0.0
|
|
spc.logger.Debug(fmt.Sprintf("CRITICAL FIX #5: Rejected opportunity: extreme positive margin (> 1000%%) %.2f%% (> 100%%)", profitMarginFloat*100))
|
|
} else if profitMarginFloat < -10.0 {
|
|
// CRITICAL FIX: Extreme negative margin (< -100%) - likely dust amounts or calc error
|
|
opportunity.ProfitMargin = 0.0
|
|
opportunity.IsExecutable = false
|
|
opportunity.RejectReason = fmt.Sprintf("unrealistic negative profit margin: %.2f%% (dust or calc error)", profitMarginFloat*100)
|
|
opportunity.Confidence = 0.0
|
|
spc.logger.Debug(fmt.Sprintf("Rejected opportunity: extreme negative margin %.2f%% (< -100%%), likely dust or calc error", profitMarginFloat*100))
|
|
} else {
|
|
// Normal range: -1000% to +1000% - allows normal arbitrage (0.01% - 0.5%)
|
|
opportunity.ProfitMargin = profitMarginFloat
|
|
}
|
|
} else {
|
|
// amountOut is zero or negative - should not happen due to earlier checks
|
|
opportunity.ProfitMargin = 0.0
|
|
opportunity.IsExecutable = false
|
|
opportunity.RejectReason = "invalid amountOut for profit margin calculation"
|
|
opportunity.Confidence = 0.0
|
|
}
|
|
|
|
// Determine if executable (considering both profit and slippage risk)
|
|
if netProfit.Sign() > 0 {
|
|
netProfitWei, _ := netProfit.Int(nil)
|
|
if netProfitWei.Cmp(spc.minProfitThreshold) >= 0 {
|
|
// Check slippage risk
|
|
if opportunity.SlippageRisk == "Extreme" {
|
|
opportunity.IsExecutable = false
|
|
opportunity.RejectReason = "extreme slippage risk"
|
|
opportunity.Confidence = 0.1
|
|
} else if slippageAnalysis != nil && !slippageAnalysis.IsAcceptable {
|
|
opportunity.IsExecutable = false
|
|
opportunity.RejectReason = fmt.Sprintf("slippage too high: %s", slippageAnalysis.Recommendation)
|
|
opportunity.Confidence = 0.2
|
|
} else {
|
|
opportunity.IsExecutable = true
|
|
opportunity.Confidence = spc.calculateConfidence(opportunity)
|
|
opportunity.RejectReason = ""
|
|
}
|
|
} else {
|
|
opportunity.IsExecutable = false
|
|
opportunity.RejectReason = "profit below minimum threshold"
|
|
opportunity.Confidence = 0.3
|
|
}
|
|
} else {
|
|
opportunity.IsExecutable = false
|
|
opportunity.RejectReason = "negative profit after gas and slippage costs"
|
|
opportunity.Confidence = 0.1
|
|
}
|
|
} else {
|
|
opportunity.IsExecutable = false
|
|
opportunity.RejectReason = "invalid swap amounts"
|
|
opportunity.Confidence = 0.0
|
|
}
|
|
|
|
spc.logger.Debug(fmt.Sprintf("Analyzed arbitrage opportunity: ID=%s, NetProfit=%s ETH, Executable=%t, Reason=%s",
|
|
opportunity.ID,
|
|
spc.FormatEther(opportunity.NetProfit),
|
|
opportunity.IsExecutable,
|
|
opportunity.RejectReason,
|
|
))
|
|
|
|
return opportunity
|
|
}
|
|
|
|
// calculateGasCost estimates the gas cost for an arbitrage transaction
|
|
func (spc *ProfitCalculator) calculateGasCost() *big.Float {
|
|
// Gas cost = Gas price * Gas limit
|
|
// Gas cost = Gas price * Gas limit
|
|
gasLimitInt64, err := security.SafeUint64ToInt64(spc.gasLimit)
|
|
if err != nil {
|
|
spc.logger.Error("Gas limit exceeds int64 maximum", "gasLimit", spc.gasLimit, "error", err)
|
|
// Use maximum safe value as fallback
|
|
gasLimitInt64 = math.MaxInt64
|
|
}
|
|
gasLimit := big.NewInt(gasLimitInt64)
|
|
currentGasPrice := spc.GetCurrentGasPrice()
|
|
gasCostWei := new(big.Int).Mul(currentGasPrice, gasLimit)
|
|
|
|
// Add 5% buffer for MEV competition (reduced from 20%)
|
|
buffer := new(big.Int).Div(gasCostWei, big.NewInt(20)) // 5%
|
|
gasCostWei.Add(gasCostWei, buffer)
|
|
|
|
// Convert to big.Float for easier calculation
|
|
gasCostFloat := new(big.Float).SetInt(gasCostWei)
|
|
// Convert from wei to ether
|
|
etherDenominator := new(big.Float).SetInt(big.NewInt(1e18))
|
|
return new(big.Float).Quo(gasCostFloat, etherDenominator)
|
|
}
|
|
|
|
// calculateConfidence calculates a confidence score for the opportunity
|
|
func (spc *ProfitCalculator) calculateConfidence(opp *SimpleOpportunity) float64 {
|
|
confidence := 0.0
|
|
|
|
// Base confidence for positive profit
|
|
if opp.NetProfit != nil && opp.NetProfit.Sign() > 0 {
|
|
confidence += 0.4
|
|
}
|
|
|
|
// Confidence based on profit margin
|
|
if opp.ProfitMargin > 0.02 { // > 2% margin
|
|
confidence += 0.3
|
|
} else if opp.ProfitMargin > 0.01 { // > 1% margin
|
|
confidence += 0.2
|
|
} else if opp.ProfitMargin > 0.005 { // > 0.5% margin
|
|
confidence += 0.1
|
|
}
|
|
|
|
// Confidence based on trade size (larger trades = more confidence)
|
|
if opp.AmountOut != nil {
|
|
amountOutFloat, _ := opp.AmountOut.Float64()
|
|
if amountOutFloat > 1000 { // > $1000 equivalent
|
|
confidence += 0.2
|
|
} else if amountOutFloat > 100 { // > $100 equivalent
|
|
confidence += 0.1
|
|
}
|
|
}
|
|
|
|
// Cap at 1.0
|
|
if confidence > 1.0 {
|
|
confidence = 1.0
|
|
}
|
|
|
|
return confidence
|
|
}
|
|
|
|
// FormatEther formats a big.Float ether amount to string (public method)
|
|
func (spc *ProfitCalculator) FormatEther(ether *big.Float) string {
|
|
if ether == nil {
|
|
return "0.000000"
|
|
}
|
|
return fmt.Sprintf("%.6f", ether)
|
|
}
|
|
|
|
// UpdateGasPrice updates the current gas price for calculations
|
|
func (spc *ProfitCalculator) UpdateGasPrice(gasPrice *big.Int) {
|
|
spc.gasPriceMutex.Lock()
|
|
defer spc.gasPriceMutex.Unlock()
|
|
|
|
spc.gasPrice = gasPrice
|
|
spc.lastGasPriceUpdate = time.Now()
|
|
spc.logger.Debug(fmt.Sprintf("Updated gas price to %s gwei",
|
|
new(big.Float).Quo(new(big.Float).SetInt(gasPrice), big.NewFloat(1e9))))
|
|
}
|
|
|
|
// GetCurrentGasPrice gets the current gas price (thread-safe)
|
|
func (spc *ProfitCalculator) GetCurrentGasPrice() *big.Int {
|
|
spc.gasPriceMutex.RLock()
|
|
defer spc.gasPriceMutex.RUnlock()
|
|
return new(big.Int).Set(spc.gasPrice)
|
|
}
|
|
|
|
// startGasPriceUpdater starts a background goroutine to update gas prices
|
|
func (spc *ProfitCalculator) startGasPriceUpdater() {
|
|
ticker := time.NewTicker(spc.gasPriceUpdateInterval)
|
|
defer ticker.Stop()
|
|
|
|
spc.logger.Info(fmt.Sprintf("Starting gas price updater with %s interval", spc.gasPriceUpdateInterval))
|
|
|
|
// Update gas price immediately on start
|
|
spc.updateGasPriceFromNetwork()
|
|
|
|
for range ticker.C {
|
|
spc.updateGasPriceFromNetwork()
|
|
}
|
|
}
|
|
|
|
// updateGasPriceFromNetwork fetches current gas price from the network
|
|
func (spc *ProfitCalculator) updateGasPriceFromNetwork() {
|
|
if spc.client == nil {
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
gasPrice, err := spc.client.SuggestGasPrice(ctx)
|
|
if err != nil {
|
|
spc.logger.Debug(fmt.Sprintf("Failed to fetch gas price from network: %v", err))
|
|
return
|
|
}
|
|
|
|
// Add MEV priority fee (50% boost for competitive transactions)
|
|
mevGasPrice := new(big.Int).Mul(gasPrice, big.NewInt(150))
|
|
mevGasPrice.Div(mevGasPrice, big.NewInt(100))
|
|
|
|
// Cap gas price for Arbitrum (typically 0.01-0.5 gwei)
|
|
// Prevents overestimation on low-gas networks
|
|
maxGasPrice := big.NewInt(500000000) // 0.5 gwei max for Arbitrum
|
|
if mevGasPrice.Cmp(maxGasPrice) > 0 {
|
|
spc.logger.Debug(fmt.Sprintf("Capping gas price at 0.5 gwei (was %s gwei)",
|
|
new(big.Float).Quo(new(big.Float).SetInt(mevGasPrice), big.NewFloat(1e9))))
|
|
mevGasPrice = maxGasPrice
|
|
}
|
|
|
|
spc.UpdateGasPrice(mevGasPrice)
|
|
}
|
|
|
|
// SetMinProfitThreshold sets the minimum profit threshold
|
|
func (spc *ProfitCalculator) SetMinProfitThreshold(threshold *big.Int) {
|
|
spc.minProfitThreshold = threshold
|
|
spc.logger.Info(fmt.Sprintf("Updated minimum profit threshold to %s ETH",
|
|
new(big.Float).Quo(new(big.Float).SetInt(threshold), big.NewFloat(1e18))))
|
|
}
|
|
|
|
// GetPriceFeedStats returns statistics about the price feed
|
|
func (spc *ProfitCalculator) GetPriceFeedStats() map[string]interface{} {
|
|
if spc.priceFeed != nil {
|
|
return spc.priceFeed.GetPriceStats()
|
|
}
|
|
return map[string]interface{}{
|
|
"status": "price feed not available",
|
|
}
|
|
}
|
|
|
|
// HasPriceFeed returns true if the calculator has an active price feed
|
|
func (spc *ProfitCalculator) HasPriceFeed() bool {
|
|
return spc.priceFeed != nil
|
|
}
|
|
|
|
// Stop gracefully shuts down the profit calculator
|
|
func (spc *ProfitCalculator) Stop() {
|
|
if spc.priceFeed != nil {
|
|
spc.priceFeed.Stop()
|
|
spc.logger.Info("Price feed stopped")
|
|
}
|
|
}
|