Files
mev-beta/orig/tests/calculation-validation/parser.go
Administrator c54c569f30 refactor: move all remaining files to orig/ directory
Completed clean root directory structure:
- Root now contains only: .git, .env, docs/, orig/
- Moved all remaining files and directories to orig/:
  - Config files (.claude, .dockerignore, .drone.yml, etc.)
  - All .env variants (except active .env)
  - Git config (.gitconfig, .github, .gitignore, etc.)
  - Tool configs (.golangci.yml, .revive.toml, etc.)
  - Documentation (*.md files, @prompts)
  - Build files (Dockerfiles, Makefile, go.mod, go.sum)
  - Docker compose files
  - All source directories (scripts, tests, tools, etc.)
  - Runtime directories (logs, monitoring, reports)
  - Dependency files (node_modules, lib, cache)
  - Special files (--delete)

- Removed empty runtime directories (bin/, data/)

V2 structure is now clean:
- docs/planning/ - V2 planning documents
- orig/ - Complete V1 codebase preserved
- .env - Active environment config (not in git)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 10:53:05 +01:00

263 lines
7.1 KiB
Go

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
}