package arbitrum import ( "encoding/json" "fmt" "math/big" "os" "sync" "time" "github.com/ethereum/go-ethereum/common" "github.com/fraktal/mev-beta/internal/logger" ) // ProfitabilityTracker tracks detailed profitability metrics and analytics type ProfitabilityTracker struct { logger *logger.Logger mu sync.RWMutex // Performance metrics totalTrades uint64 successfulTrades uint64 failedTrades uint64 totalProfit *big.Int totalGasCost *big.Int netProfit *big.Int // Time-based metrics hourlyStats map[string]*HourlyStats dailyStats map[string]*DailyStats weeklyStats map[string]*WeeklyStats // Token pair analytics tokenPairStats map[string]*TokenPairStats // Exchange analytics exchangeStats map[string]*ExchangeStats // Opportunity analytics opportunityStats *OpportunityStats // File handles for logging profitLogFile *os.File opportunityLogFile *os.File performanceLogFile *os.File startTime time.Time } // HourlyStats represents hourly performance statistics type HourlyStats struct { Hour string `json:"hour"` Trades uint64 `json:"trades"` SuccessfulTrades uint64 `json:"successful_trades"` TotalProfit string `json:"total_profit_wei"` TotalGasCost string `json:"total_gas_cost_wei"` NetProfit string `json:"net_profit_wei"` AverageROI float64 `json:"average_roi"` BestTrade string `json:"best_trade_profit_wei"` WorstTrade string `json:"worst_trade_loss_wei"` Timestamp time.Time `json:"timestamp"` } // DailyStats represents daily performance statistics type DailyStats struct { Date string `json:"date"` Trades uint64 `json:"trades"` SuccessfulTrades uint64 `json:"successful_trades"` TotalProfit string `json:"total_profit_wei"` TotalGasCost string `json:"total_gas_cost_wei"` NetProfit string `json:"net_profit_wei"` ProfitUSD float64 `json:"profit_usd"` ROI float64 `json:"roi"` SuccessRate float64 `json:"success_rate"` CapitalEfficiency float64 `json:"capital_efficiency"` Timestamp time.Time `json:"timestamp"` } // WeeklyStats represents weekly performance statistics type WeeklyStats struct { Week string `json:"week"` Trades uint64 `json:"trades"` SuccessfulTrades uint64 `json:"successful_trades"` TotalProfit string `json:"total_profit_wei"` TotalGasCost string `json:"total_gas_cost_wei"` NetProfit string `json:"net_profit_wei"` ProfitUSD float64 `json:"profit_usd"` WeeklyROI float64 `json:"weekly_roi"` BestDay string `json:"best_day"` WorstDay string `json:"worst_day"` Timestamp time.Time `json:"timestamp"` } // TokenPairStats represents statistics for specific token pairs type TokenPairStats struct { TokenIn string `json:"token_in"` TokenOut string `json:"token_out"` Trades uint64 `json:"trades"` SuccessfulTrades uint64 `json:"successful_trades"` TotalProfit string `json:"total_profit_wei"` TotalGasCost string `json:"total_gas_cost_wei"` NetProfit string `json:"net_profit_wei"` AverageProfit string `json:"average_profit_wei"` BestTrade string `json:"best_trade_profit_wei"` SuccessRate float64 `json:"success_rate"` AverageExecutionTime time.Duration `json:"average_execution_time"` LastTrade time.Time `json:"last_trade"` } // ExchangeStats represents statistics for specific exchanges type ExchangeStats struct { Exchange string `json:"exchange"` Trades uint64 `json:"trades"` SuccessfulTrades uint64 `json:"successful_trades"` TotalProfit string `json:"total_profit_wei"` TotalGasCost string `json:"total_gas_cost_wei"` NetProfit string `json:"net_profit_wei"` SuccessRate float64 `json:"success_rate"` AverageProfit string `json:"average_profit_wei"` LastTrade time.Time `json:"last_trade"` } // OpportunityStats represents overall opportunity statistics type OpportunityStats struct { TotalOpportunities uint64 `json:"total_opportunities"` ExecutedOpportunities uint64 `json:"executed_opportunities"` SkippedDueToCapital uint64 `json:"skipped_due_to_capital"` SkippedDueToGas uint64 `json:"skipped_due_to_gas"` SkippedDueToRisk uint64 `json:"skipped_due_to_risk"` ExecutionRate float64 `json:"execution_rate"` LastOpportunity time.Time `json:"last_opportunity"` } // TradeEvent represents a trade event for logging type TradeEvent struct { Timestamp time.Time `json:"timestamp"` TradeID string `json:"trade_id"` Type string `json:"type"` // "arbitrage", "sandwich", "liquidation" TokenIn string `json:"token_in"` TokenOut string `json:"token_out"` AmountIn string `json:"amount_in_wei"` ExpectedProfit string `json:"expected_profit_wei"` ActualProfit string `json:"actual_profit_wei"` GasCost string `json:"gas_cost_wei"` NetProfit string `json:"net_profit_wei"` ROI float64 `json:"roi"` ExecutionTime string `json:"execution_time"` Success bool `json:"success"` Exchange string `json:"exchange"` Pool string `json:"pool"` TxHash string `json:"tx_hash"` FailureReason string `json:"failure_reason,omitempty"` } // NewProfitabilityTracker creates a new profitability tracker func NewProfitabilityTracker(logger *logger.Logger) (*ProfitabilityTracker, error) { // Create log files profitFile, err := os.OpenFile("logs/profitability.jsonl", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return nil, fmt.Errorf("failed to create profit log file: %w", err) } opportunityFile, err := os.OpenFile("logs/opportunities.jsonl", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return nil, fmt.Errorf("failed to create opportunity log file: %w", err) } performanceFile, err := os.OpenFile("logs/performance.jsonl", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return nil, fmt.Errorf("failed to create performance log file: %w", err) } return &ProfitabilityTracker{ logger: logger, totalProfit: big.NewInt(0), totalGasCost: big.NewInt(0), netProfit: big.NewInt(0), hourlyStats: make(map[string]*HourlyStats), dailyStats: make(map[string]*DailyStats), weeklyStats: make(map[string]*WeeklyStats), tokenPairStats: make(map[string]*TokenPairStats), exchangeStats: make(map[string]*ExchangeStats), opportunityStats: &OpportunityStats{}, profitLogFile: profitFile, opportunityLogFile: opportunityFile, performanceLogFile: performanceFile, startTime: time.Now(), }, nil } // LogTrade logs a completed trade and updates all relevant statistics func (pt *ProfitabilityTracker) LogTrade(trade *TradeEvent) { pt.mu.Lock() defer pt.mu.Unlock() // Update overall metrics pt.totalTrades++ if trade.Success { pt.successfulTrades++ if actualProfit, ok := new(big.Int).SetString(trade.ActualProfit, 10); ok { pt.totalProfit.Add(pt.totalProfit, actualProfit) } } else { pt.failedTrades++ } if gasCost, ok := new(big.Int).SetString(trade.GasCost, 10); ok { pt.totalGasCost.Add(pt.totalGasCost, gasCost) } pt.netProfit = new(big.Int).Sub(pt.totalProfit, pt.totalGasCost) // Update time-based statistics pt.updateHourlyStats(trade) pt.updateDailyStats(trade) pt.updateWeeklyStats(trade) // Update token pair statistics pt.updateTokenPairStats(trade) // Update exchange statistics pt.updateExchangeStats(trade) // Log to file pt.logTradeToFile(trade) // Log performance metrics every 10 trades if pt.totalTrades%10 == 0 { pt.logPerformanceMetrics() } } // LogOpportunity logs an arbitrage opportunity (executed or skipped) func (pt *ProfitabilityTracker) LogOpportunity(tokenIn, tokenOut common.Address, profit *big.Int, executed bool, skipReason string) { pt.mu.Lock() defer pt.mu.Unlock() pt.opportunityStats.TotalOpportunities++ pt.opportunityStats.LastOpportunity = time.Now() if executed { pt.opportunityStats.ExecutedOpportunities++ } else { switch skipReason { case "capital": pt.opportunityStats.SkippedDueToCapital++ case "gas": pt.opportunityStats.SkippedDueToGas++ case "risk": pt.opportunityStats.SkippedDueToRisk++ } } // Update execution rate if pt.opportunityStats.TotalOpportunities > 0 { pt.opportunityStats.ExecutionRate = float64(pt.opportunityStats.ExecutedOpportunities) / float64(pt.opportunityStats.TotalOpportunities) } // Log to opportunity file opportunityEvent := map[string]interface{}{ "timestamp": time.Now(), "token_in": tokenIn.Hex(), "token_out": tokenOut.Hex(), "profit_wei": profit.String(), "executed": executed, "skip_reason": skipReason, } if data, err := json.Marshal(opportunityEvent); err == nil { if _, writeErr := pt.opportunityLogFile.Write(append(data, '\n')); writeErr != nil { pt.logger.Error("Failed to write opportunity log", "error", writeErr) } if syncErr := pt.opportunityLogFile.Sync(); syncErr != nil { pt.logger.Error("Failed to sync opportunity log", "error", syncErr) } } } // GetRealTimeStats returns current real-time statistics func (pt *ProfitabilityTracker) GetRealTimeStats() map[string]interface{} { pt.mu.RLock() defer pt.mu.RUnlock() runtime := time.Since(pt.startTime) successRate := 0.0 if pt.totalTrades > 0 { successRate = float64(pt.successfulTrades) / float64(pt.totalTrades) } avgTradesPerHour := 0.0 if runtime.Hours() > 0 { avgTradesPerHour = float64(pt.totalTrades) / runtime.Hours() } profitUSD := pt.weiToUSD(pt.netProfit) dailyProfitProjection := profitUSD * (24.0 / runtime.Hours()) return map[string]interface{}{ "runtime_hours": runtime.Hours(), "total_trades": pt.totalTrades, "successful_trades": pt.successfulTrades, "failed_trades": pt.failedTrades, "success_rate": successRate, "total_profit_wei": pt.totalProfit.String(), "total_gas_cost_wei": pt.totalGasCost.String(), "net_profit_wei": pt.netProfit.String(), "net_profit_usd": profitUSD, "daily_profit_projection": dailyProfitProjection, "avg_trades_per_hour": avgTradesPerHour, "execution_rate": pt.opportunityStats.ExecutionRate, "total_opportunities": pt.opportunityStats.TotalOpportunities, "executed_opportunities": pt.opportunityStats.ExecutedOpportunities, } } // GetTopTokenPairs returns the most profitable token pairs func (pt *ProfitabilityTracker) GetTopTokenPairs(limit int) []*TokenPairStats { pt.mu.RLock() defer pt.mu.RUnlock() var pairs []*TokenPairStats for _, stats := range pt.tokenPairStats { pairs = append(pairs, stats) } // Sort by net profit for i := 0; i < len(pairs)-1; i++ { for j := i + 1; j < len(pairs); j++ { profitI, _ := new(big.Int).SetString(pairs[i].NetProfit, 10) profitJ, _ := new(big.Int).SetString(pairs[j].NetProfit, 10) if profitI.Cmp(profitJ) < 0 { pairs[i], pairs[j] = pairs[j], pairs[i] } } } if len(pairs) > limit { pairs = pairs[:limit] } return pairs } // Private helper methods func (pt *ProfitabilityTracker) updateHourlyStats(trade *TradeEvent) { hour := trade.Timestamp.Format("2006-01-02-15") stats, exists := pt.hourlyStats[hour] if !exists { stats = &HourlyStats{ Hour: hour, Timestamp: trade.Timestamp, } pt.hourlyStats[hour] = stats } stats.Trades++ if trade.Success { stats.SuccessfulTrades++ if profit, ok := new(big.Int).SetString(trade.ActualProfit, 10); ok && profit.Sign() > 0 { currentProfit, _ := new(big.Int).SetString(stats.TotalProfit, 10) newProfit := new(big.Int).Add(currentProfit, profit) stats.TotalProfit = newProfit.String() } } if gasCost, ok := new(big.Int).SetString(trade.GasCost, 10); ok { currentGas, _ := new(big.Int).SetString(stats.TotalGasCost, 10) newGas := new(big.Int).Add(currentGas, gasCost) stats.TotalGasCost = newGas.String() } // Calculate net profit profit, _ := new(big.Int).SetString(stats.TotalProfit, 10) gasCost, _ := new(big.Int).SetString(stats.TotalGasCost, 10) netProfit := new(big.Int).Sub(profit, gasCost) stats.NetProfit = netProfit.String() } func (pt *ProfitabilityTracker) updateDailyStats(trade *TradeEvent) { date := trade.Timestamp.Format("2006-01-02") stats, exists := pt.dailyStats[date] if !exists { stats = &DailyStats{ Date: date, Timestamp: trade.Timestamp, } pt.dailyStats[date] = stats } stats.Trades++ if trade.Success { stats.SuccessfulTrades++ if profit, ok := new(big.Int).SetString(trade.ActualProfit, 10); ok && profit.Sign() > 0 { currentProfit, _ := new(big.Int).SetString(stats.TotalProfit, 10) newProfit := new(big.Int).Add(currentProfit, profit) stats.TotalProfit = newProfit.String() } } if gasCost, ok := new(big.Int).SetString(trade.GasCost, 10); ok { currentGas, _ := new(big.Int).SetString(stats.TotalGasCost, 10) newGas := new(big.Int).Add(currentGas, gasCost) stats.TotalGasCost = newGas.String() } // Calculate derived metrics profit, _ := new(big.Int).SetString(stats.TotalProfit, 10) gasCost, _ := new(big.Int).SetString(stats.TotalGasCost, 10) netProfit := new(big.Int).Sub(profit, gasCost) stats.NetProfit = netProfit.String() stats.ProfitUSD = pt.weiToUSD(netProfit) if stats.Trades > 0 { stats.SuccessRate = float64(stats.SuccessfulTrades) / float64(stats.Trades) } } func (pt *ProfitabilityTracker) updateWeeklyStats(trade *TradeEvent) { // Implementation similar to daily stats but for weekly periods year, week := trade.Timestamp.ISOWeek() weekKey := fmt.Sprintf("%d-W%02d", year, week) stats, exists := pt.weeklyStats[weekKey] if !exists { stats = &WeeklyStats{ Week: weekKey, Timestamp: trade.Timestamp, } pt.weeklyStats[weekKey] = stats } stats.Trades++ if trade.Success { stats.SuccessfulTrades++ } } func (pt *ProfitabilityTracker) updateTokenPairStats(trade *TradeEvent) { pairKey := fmt.Sprintf("%s-%s", trade.TokenIn, trade.TokenOut) stats, exists := pt.tokenPairStats[pairKey] if !exists { stats = &TokenPairStats{ TokenIn: trade.TokenIn, TokenOut: trade.TokenOut, } pt.tokenPairStats[pairKey] = stats } stats.Trades++ stats.LastTrade = trade.Timestamp if trade.Success { stats.SuccessfulTrades++ if profit, ok := new(big.Int).SetString(trade.ActualProfit, 10); ok && profit.Sign() > 0 { currentProfit, _ := new(big.Int).SetString(stats.TotalProfit, 10) newProfit := new(big.Int).Add(currentProfit, profit) stats.TotalProfit = newProfit.String() } } if gasCost, ok := new(big.Int).SetString(trade.GasCost, 10); ok { currentGas, _ := new(big.Int).SetString(stats.TotalGasCost, 10) newGas := new(big.Int).Add(currentGas, gasCost) stats.TotalGasCost = newGas.String() } // Calculate derived metrics profit, _ := new(big.Int).SetString(stats.TotalProfit, 10) gasCost, _ := new(big.Int).SetString(stats.TotalGasCost, 10) netProfit := new(big.Int).Sub(profit, gasCost) stats.NetProfit = netProfit.String() if stats.Trades > 0 { stats.SuccessRate = float64(stats.SuccessfulTrades) / float64(stats.Trades) } if stats.SuccessfulTrades > 0 { avgProfit := new(big.Int).Div(profit, big.NewInt(int64(stats.SuccessfulTrades))) stats.AverageProfit = avgProfit.String() } } func (pt *ProfitabilityTracker) updateExchangeStats(trade *TradeEvent) { stats, exists := pt.exchangeStats[trade.Exchange] if !exists { stats = &ExchangeStats{ Exchange: trade.Exchange, } pt.exchangeStats[trade.Exchange] = stats } stats.Trades++ stats.LastTrade = trade.Timestamp if trade.Success { stats.SuccessfulTrades++ if profit, ok := new(big.Int).SetString(trade.ActualProfit, 10); ok && profit.Sign() > 0 { currentProfit, _ := new(big.Int).SetString(stats.TotalProfit, 10) newProfit := new(big.Int).Add(currentProfit, profit) stats.TotalProfit = newProfit.String() } } if stats.Trades > 0 { stats.SuccessRate = float64(stats.SuccessfulTrades) / float64(stats.Trades) } } func (pt *ProfitabilityTracker) logTradeToFile(trade *TradeEvent) { if data, err := json.Marshal(trade); err == nil { if _, writeErr := pt.profitLogFile.Write(append(data, '\n')); writeErr != nil { pt.logger.Error("Failed to write trade data to profit log", "error", writeErr) } if syncErr := pt.profitLogFile.Sync(); syncErr != nil { pt.logger.Error("Failed to sync profit log file", "error", syncErr) } } else { pt.logger.Error("Failed to marshal trade event for logging", "error", err) } } func (pt *ProfitabilityTracker) logPerformanceMetrics() { metrics := pt.GetRealTimeStats() metrics["timestamp"] = time.Now() metrics["type"] = "performance_snapshot" if data, err := json.Marshal(metrics); err == nil { if _, writeErr := pt.performanceLogFile.Write(append(data, '\n')); writeErr != nil { pt.logger.Error("Failed to write performance metrics", "error", writeErr) } if syncErr := pt.performanceLogFile.Sync(); syncErr != nil { pt.logger.Error("Failed to sync performance log file", "error", syncErr) } } else { pt.logger.Error("Failed to marshal performance metrics", "error", err) } } func (pt *ProfitabilityTracker) weiToUSD(wei *big.Int) float64 { // Assume ETH = $2000 for calculations ethPrice := 2000.0 ethAmount := new(big.Float).Quo(new(big.Float).SetInt(wei), big.NewFloat(1e18)) ethFloat, _ := ethAmount.Float64() return ethFloat * ethPrice } // Close closes all log files func (pt *ProfitabilityTracker) Close() error { var errors []error if pt.profitLogFile != nil { if err := pt.profitLogFile.Close(); err != nil { errors = append(errors, err) } } if pt.opportunityLogFile != nil { if err := pt.opportunityLogFile.Close(); err != nil { errors = append(errors, err) } } if pt.performanceLogFile != nil { if err := pt.performanceLogFile.Close(); err != nil { errors = append(errors, err) } } if len(errors) > 0 { return fmt.Errorf("errors closing profitability tracker: %v", errors) } return nil }