package calculation_validation import ( "bufio" "fmt" "math/big" "os" "regexp" "strconv" "strings" "time" ) // LogParser parses arbitrage opportunity data from log files type LogParser struct { executablePattern *regexp.Regexp profitPattern *regexp.Regexp thresholdPattern *regexp.Regexp v3CalcPattern *regexp.Regexp opportunityPattern *regexp.Regexp } // NewLogParser creates a new log parser func NewLogParser() *LogParser { return &LogParser{ executablePattern: regexp.MustCompile(`EXECUTABLE OPPORTUNITY: ID=([^,]+), Profit=([0-9.]+) ETH \(threshold=([0-9.]+) ETH\)`), profitPattern: regexp.MustCompile(`Estimated Profit: \$([0-9,.]+) USD`), thresholdPattern: regexp.MustCompile(`Profit threshold check: netProfit=([0-9.]+) ETH, minThreshold=([0-9.]+) ETH`), v3CalcPattern: regexp.MustCompile(`V3 calculation: amountIn=([0-9]+), amountOut=([0-9]+), fee=([0-9]+), finalOut=([0-9]+)`), opportunityPattern: regexp.MustCompile(`ARBITRAGE OPPORTUNITY DETECTED`), } } // ParseExecutableOpportunities parses executable opportunities from log file func (lp *LogParser) ParseExecutableOpportunities(logFile string) ([]*OpportunityTestData, error) { file, err := os.Open(logFile) if err != nil { return nil, fmt.Errorf("failed to open log file: %w", err) } defer file.Close() var opportunities []*OpportunityTestData scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() // Parse executable opportunity if matches := lp.executablePattern.FindStringSubmatch(line); matches != nil { opp := &OpportunityTestData{ ID: matches[1], Timestamp: time.Now(), // Would need to parse from log timestamp IsExecutable: true, } // Parse profit profit, err := strconv.ParseFloat(matches[2], 64) if err == nil { opp.NetProfitETH = big.NewFloat(profit) } // Parse threshold threshold, err := strconv.ParseFloat(matches[3], 64) if err == nil { opp.ThresholdCheck = &ThresholdCheck{ NetProfit: big.NewFloat(profit), MinThreshold: big.NewFloat(threshold), Passed: profit >= threshold, } } opportunities = append(opportunities, opp) } } if err := scanner.Err(); err != nil { return nil, fmt.Errorf("error reading log file: %w", err) } return opportunities, nil } // ParseV3Calculations parses V3 swap calculations from log file func (lp *LogParser) ParseV3Calculations(logFile string) ([]SwapCalculation, error) { file, err := os.Open(logFile) if err != nil { return nil, fmt.Errorf("failed to open log file: %w", err) } defer file.Close() var calculations []SwapCalculation scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() if matches := lp.v3CalcPattern.FindStringSubmatch(line); matches != nil { amountIn, _ := new(big.Int).SetString(matches[1], 10) amountOut, _ := new(big.Int).SetString(matches[2], 10) fee, _ := strconv.ParseUint(matches[3], 10, 32) finalOut, _ := new(big.Int).SetString(matches[4], 10) calculations = append(calculations, SwapCalculation{ AmountIn: amountIn, AmountOut: amountOut, Fee: uint32(fee), FinalOut: finalOut, }) } } if err := scanner.Err(); err != nil { return nil, fmt.Errorf("error reading log file: %w", err) } return calculations, nil } // ParseThresholdChecks parses profit threshold validation checks func (lp *LogParser) ParseThresholdChecks(logFile string) ([]*ThresholdCheck, error) { file, err := os.Open(logFile) if err != nil { return nil, fmt.Errorf("failed to open log file: %w", err) } defer file.Close() var checks []*ThresholdCheck scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() if matches := lp.thresholdPattern.FindStringSubmatch(line); matches != nil { netProfit, _ := strconv.ParseFloat(matches[1], 64) minThreshold, _ := strconv.ParseFloat(matches[2], 64) checks = append(checks, &ThresholdCheck{ NetProfit: big.NewFloat(netProfit), MinThreshold: big.NewFloat(minThreshold), Passed: netProfit >= minThreshold, }) } } if err := scanner.Err(); err != nil { return nil, fmt.Errorf("error reading log file: %w", err) } return checks, nil } // ExtractProfitValues extracts all profit values from opportunities func ExtractProfitValues(opportunities []*OpportunityTestData) []*big.Float { var profits []*big.Float for _, opp := range opportunities { if opp.NetProfitETH != nil { profits = append(profits, opp.NetProfitETH) } } return profits } // CalculateStatistics calculates statistics from profit values func CalculateStatistics(profits []*big.Float) (total, average, max, min *big.Float) { if len(profits) == 0 { return big.NewFloat(0), big.NewFloat(0), big.NewFloat(0), big.NewFloat(0) } total = big.NewFloat(0) max = new(big.Float).Set(profits[0]) min = new(big.Float).Set(profits[0]) for _, profit := range profits { total.Add(total, profit) if profit.Cmp(max) > 0 { max = new(big.Float).Set(profit) } if profit.Cmp(min) < 0 { min = new(big.Float).Set(profit) } } average = new(big.Float).Quo(total, big.NewFloat(float64(len(profits)))) return total, average, max, min } // ParseOpportunityDetails parses detailed opportunity information from multi-line log entries func (lp *LogParser) ParseOpportunityDetails(logFile string) ([]*OpportunityTestData, error) { file, err := os.Open(logFile) if err != nil { return nil, fmt.Errorf("failed to open log file: %w", err) } defer file.Close() var opportunities []*OpportunityTestData var currentOpp *OpportunityTestData scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() // Start of new opportunity if lp.opportunityPattern.MatchString(line) { if currentOpp != nil { opportunities = append(opportunities, currentOpp) } currentOpp = &OpportunityTestData{ Timestamp: time.Now(), } continue } if currentOpp == nil { continue } // Parse various fields if strings.Contains(line, "Transaction:") { parts := strings.Split(line, ":") if len(parts) > 1 { currentOpp.TransactionHash = strings.TrimSpace(parts[1]) } } if matches := lp.profitPattern.FindStringSubmatch(line); matches != nil { profitStr := strings.ReplaceAll(matches[1], ",", "") profitUSD, _ := strconv.ParseFloat(profitStr, 64) currentOpp.NetProfitUSD = big.NewFloat(profitUSD) } if strings.Contains(line, "netProfitETH:") { parts := strings.Split(line, ":") if len(parts) > 1 { profitStr := strings.TrimSpace(parts[1]) profit, _ := strconv.ParseFloat(profitStr, 64) currentOpp.NetProfitETH = big.NewFloat(profit) } } if strings.Contains(line, "isExecutable:") { currentOpp.IsExecutable = strings.Contains(line, "isExecutable:true") } if strings.Contains(line, "rejectReason:") { parts := strings.SplitN(line, ":", 2) if len(parts) > 1 { currentOpp.RejectReason = strings.TrimSpace(parts[1]) } } } // Don't forget the last opportunity if currentOpp != nil { opportunities = append(opportunities, currentOpp) } if err := scanner.Err(); err != nil { return nil, fmt.Errorf("error reading log file: %w", err) } return opportunities, nil }