- 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
208 lines
6.6 KiB
Go
208 lines
6.6 KiB
Go
package marketmanager
|
|
|
|
import (
|
|
"math/big"
|
|
"sort"
|
|
)
|
|
|
|
// 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,
|
|
}
|
|
}
|
|
|
|
// ArbitrageOpportunity represents a detected arbitrage opportunity
|
|
type ArbitrageOpportunity struct {
|
|
Market1 *Market
|
|
Market2 *Market
|
|
Path []string // Token path
|
|
Profit *big.Int // Estimated profit in wei
|
|
GasEstimate *big.Int // Estimated gas cost in wei
|
|
ROI float64 // Return on investment percentage
|
|
InputAmount *big.Int // Required input amount
|
|
}
|
|
|
|
// DetectArbitrageOpportunities detects arbitrage opportunities among markets with the same rawTicker
|
|
func (ad *ArbitrageDetector) DetectArbitrageOpportunities(markets map[string]*Market) []*ArbitrageOpportunity {
|
|
var opportunities []*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) *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 arbitrage opportunity
|
|
return &ArbitrageOpportunity{
|
|
Market1: market1,
|
|
Market2: market2,
|
|
Path: []string{market1.Token0.Hex(), market1.Token1.Hex()},
|
|
Profit: netProfit,
|
|
GasEstimate: gasCost,
|
|
ROI: roi,
|
|
InputAmount: optimalTradeSize,
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|