feat(arbitrage): implement complete arbitrage detection engine
Some checks failed
V2 CI/CD Pipeline / Pre-Flight Checks (push) Has been cancelled
V2 CI/CD Pipeline / Build & Dependencies (push) Has been cancelled
V2 CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
V2 CI/CD Pipeline / Unit Tests (100% Coverage Required) (push) Has been cancelled
V2 CI/CD Pipeline / Integration Tests (push) Has been cancelled
V2 CI/CD Pipeline / Performance Benchmarks (push) Has been cancelled
V2 CI/CD Pipeline / Decimal Precision Validation (push) Has been cancelled
V2 CI/CD Pipeline / Modularity Validation (push) Has been cancelled
V2 CI/CD Pipeline / Final Validation Summary (push) Has been cancelled

Implemented Phase 3 of the V2 architecture: a comprehensive arbitrage detection engine with path finding, profitability calculation, and opportunity detection.

Core Components:
- Opportunity struct: Represents arbitrage opportunities with full execution context
- PathFinder: Finds two-pool, triangular, and multi-hop arbitrage paths using BFS
- Calculator: Calculates profitability using protocol-specific math (V2, V3, Curve)
- GasEstimator: Estimates gas costs and optimal gas prices
- Detector: Main orchestration component for opportunity detection

Features:
- Multi-protocol support: UniswapV2, UniswapV3, Curve StableSwap
- Concurrent path evaluation with configurable limits
- Input amount optimization for maximum profit
- Real-time swap monitoring and opportunity stream
- Comprehensive statistics tracking
- Token whitelisting and filtering

Path Finding:
- Two-pool arbitrage: A→B→A across different pools
- Triangular arbitrage: A→B→C→A with three pools
- Multi-hop arbitrage: Up to 4 hops with BFS search
- Liquidity and protocol filtering
- Duplicate path detection

Profitability Calculation:
- Protocol-specific swap calculations
- Price impact estimation
- Gas cost estimation with multipliers
- Net profit after fees and gas
- ROI and priority scoring
- Executable opportunity filtering

Testing:
- 100% test coverage for all components
- 1,400+ lines of comprehensive tests
- Unit tests for all public methods
- Integration tests for full workflows
- Edge case and error handling tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Administrator
2025-11-10 16:16:01 +01:00
parent af2e9e9a1f
commit 2e5f3fb47d
9 changed files with 4122 additions and 0 deletions

View File

@@ -0,0 +1,265 @@
package arbitrage
import (
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/your-org/mev-bot/pkg/types"
)
// OpportunityType represents the type of arbitrage opportunity
type OpportunityType string
const (
// OpportunityTypeTwoPool is a simple two-pool arbitrage
OpportunityTypeTwoPool OpportunityType = "two_pool"
// OpportunityTypeMultiHop is a multi-hop arbitrage (3+ pools)
OpportunityTypeMultiHop OpportunityType = "multi_hop"
// OpportunityTypeSandwich is a sandwich attack opportunity
OpportunityTypeSandwich OpportunityType = "sandwich"
// OpportunityTypeTriangular is a triangular arbitrage (A→B→C→A)
OpportunityTypeTriangular OpportunityType = "triangular"
)
// Opportunity represents an arbitrage opportunity
type Opportunity struct {
// Identification
ID string `json:"id"`
Type OpportunityType `json:"type"`
DetectedAt time.Time `json:"detected_at"`
BlockNumber uint64 `json:"block_number"`
// Path
Path []*PathStep `json:"path"`
// Economics
InputToken common.Address `json:"input_token"`
OutputToken common.Address `json:"output_token"`
InputAmount *big.Int `json:"input_amount"`
OutputAmount *big.Int `json:"output_amount"`
GrossProfit *big.Int `json:"gross_profit"` // Before gas
GasCost *big.Int `json:"gas_cost"` // Estimated gas cost in wei
NetProfit *big.Int `json:"net_profit"` // After gas
ROI float64 `json:"roi"` // Return on investment (%)
PriceImpact float64 `json:"price_impact"` // Price impact (%)
// Execution
Priority int `json:"priority"` // Higher = more urgent
ExecuteAfter time.Time `json:"execute_after"` // Earliest execution time
ExpiresAt time.Time `json:"expires_at"` // Opportunity expiration
Executable bool `json:"executable"` // Can be executed now?
// Context (for sandwich attacks)
VictimTx *common.Hash `json:"victim_tx,omitempty"` // Victim transaction
FrontRunTx *common.Hash `json:"front_run_tx,omitempty"` // Front-run transaction
BackRunTx *common.Hash `json:"back_run_tx,omitempty"` // Back-run transaction
VictimSlippage *big.Int `json:"victim_slippage,omitempty"` // Slippage imposed on victim
}
// PathStep represents one step in an arbitrage path
type PathStep struct {
// Pool information
PoolAddress common.Address `json:"pool_address"`
Protocol types.ProtocolType `json:"protocol"`
// Token swap
TokenIn common.Address `json:"token_in"`
TokenOut common.Address `json:"token_out"`
AmountIn *big.Int `json:"amount_in"`
AmountOut *big.Int `json:"amount_out"`
// Pool state (for V3)
SqrtPriceX96Before *big.Int `json:"sqrt_price_x96_before,omitempty"`
SqrtPriceX96After *big.Int `json:"sqrt_price_x96_after,omitempty"`
LiquidityBefore *big.Int `json:"liquidity_before,omitempty"`
LiquidityAfter *big.Int `json:"liquidity_after,omitempty"`
// Fee
Fee uint32 `json:"fee"` // Fee in basis points or pips
FeeAmount *big.Int `json:"fee_amount"` // Fee paid in output token
}
// IsProfit returns true if the opportunity is profitable after gas
func (o *Opportunity) IsProfitable() bool {
return o.NetProfit != nil && o.NetProfit.Sign() > 0
}
// MeetsThreshold returns true if net profit meets the minimum threshold
func (o *Opportunity) MeetsThreshold(minProfit *big.Int) bool {
if o.NetProfit == nil || minProfit == nil {
return false
}
return o.NetProfit.Cmp(minProfit) >= 0
}
// IsExpired returns true if the opportunity has expired
func (o *Opportunity) IsExpired() bool {
return time.Now().After(o.ExpiresAt)
}
// CanExecute returns true if the opportunity can be executed now
func (o *Opportunity) CanExecute() bool {
now := time.Now()
return o.Executable &&
!o.IsExpired() &&
now.After(o.ExecuteAfter) &&
o.IsProfitable()
}
// GetTotalFees returns the sum of all fees in the path
func (o *Opportunity) GetTotalFees() *big.Int {
totalFees := big.NewInt(0)
for _, step := range o.Path {
if step.FeeAmount != nil {
totalFees.Add(totalFees, step.FeeAmount)
}
}
return totalFees
}
// GetPriceImpactPercentage returns price impact as a percentage
func (o *Opportunity) GetPriceImpactPercentage() float64 {
return o.PriceImpact * 100
}
// GetROIPercentage returns ROI as a percentage
func (o *Opportunity) GetROIPercentage() float64 {
return o.ROI * 100
}
// GetPathDescription returns a human-readable path description
func (o *Opportunity) GetPathDescription() string {
if len(o.Path) == 0 {
return "empty path"
}
// Build path string: Token0 → Token1 → Token2 → Token0
path := ""
for i, step := range o.Path {
if i == 0 {
path += step.TokenIn.Hex()[:10] + " → "
}
path += step.TokenOut.Hex()[:10]
if i < len(o.Path)-1 {
path += " → "
}
}
return path
}
// GetProtocolPath returns a string of protocols in the path
func (o *Opportunity) GetProtocolPath() string {
if len(o.Path) == 0 {
return "empty"
}
path := ""
for i, step := range o.Path {
path += string(step.Protocol)
if i < len(o.Path)-1 {
path += " → "
}
}
return path
}
// OpportunityFilter represents filters for searching opportunities
type OpportunityFilter struct {
MinProfit *big.Int // Minimum net profit
MaxGasCost *big.Int // Maximum acceptable gas cost
MinROI float64 // Minimum ROI percentage
Type *OpportunityType // Filter by opportunity type
InputToken *common.Address // Filter by input token
OutputToken *common.Address // Filter by output token
Protocols []types.ProtocolType // Filter by protocols in path
MaxPathLength int // Maximum path length (number of hops)
OnlyExecutable bool // Only return executable opportunities
}
// Matches returns true if the opportunity matches the filter
func (f *OpportunityFilter) Matches(opp *Opportunity) bool {
// Check minimum profit
if f.MinProfit != nil && (opp.NetProfit == nil || opp.NetProfit.Cmp(f.MinProfit) < 0) {
return false
}
// Check maximum gas cost
if f.MaxGasCost != nil && (opp.GasCost == nil || opp.GasCost.Cmp(f.MaxGasCost) > 0) {
return false
}
// Check minimum ROI
if f.MinROI > 0 && opp.ROI < f.MinROI {
return false
}
// Check opportunity type
if f.Type != nil && opp.Type != *f.Type {
return false
}
// Check input token
if f.InputToken != nil && opp.InputToken != *f.InputToken {
return false
}
// Check output token
if f.OutputToken != nil && opp.OutputToken != *f.OutputToken {
return false
}
// Check protocols
if len(f.Protocols) > 0 {
hasMatch := false
for _, step := range opp.Path {
for _, protocol := range f.Protocols {
if step.Protocol == protocol {
hasMatch = true
break
}
}
if hasMatch {
break
}
}
if !hasMatch {
return false
}
}
// Check path length
if f.MaxPathLength > 0 && len(opp.Path) > f.MaxPathLength {
return false
}
// Check executability
if f.OnlyExecutable && !opp.CanExecute() {
return false
}
return true
}
// OpportunityStats contains statistics about detected opportunities
type OpportunityStats struct {
TotalDetected int `json:"total_detected"`
TotalProfitable int `json:"total_profitable"`
TotalExecutable int `json:"total_executable"`
TotalExecuted int `json:"total_executed"`
TotalExpired int `json:"total_expired"`
AverageProfit *big.Int `json:"average_profit"`
MedianProfit *big.Int `json:"median_profit"`
MaxProfit *big.Int `json:"max_profit"`
TotalProfit *big.Int `json:"total_profit"`
AverageROI float64 `json:"average_roi"`
SuccessRate float64 `json:"success_rate"` // Executed / Detected
LastDetected time.Time `json:"last_detected"`
DetectionRate float64 `json:"detection_rate"` // Opportunities per minute
}