Files
mev-beta/orig/tools/profitability-audit/internal/profitability_auditor.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

512 lines
16 KiB
Go

package internal
import (
"context"
"encoding/json"
"fmt"
"math/big"
"os"
"path/filepath"
"time"
"github.com/fraktal/mev-beta/pkg/math"
)
// Config holds configuration for the profitability auditor
type Config struct {
MinProfitBP float64
MaxSlippageBP float64
OutputDir string
Verbose bool
ScenariosFile string
}
// ProfitabilityAuditor audits profit calculations and arbitrage opportunities
type ProfitabilityAuditor struct {
config *Config
converter *math.DecimalConverter
calculator *math.ArbitrageCalculator
scenarios *ProfitabilityScenarios
results map[string]*ExchangeProfitResult
}
// ProfitabilityScenarios defines test scenarios for profit validation
type ProfitabilityScenarios struct {
Version string `json:"version"`
Scenarios map[string]*ProfitabilityTest `json:"scenarios"`
}
// ProfitabilityTest represents a profit calculation test case
type ProfitabilityTest struct {
Name string `json:"name"`
Description string `json:"description"`
Exchange string `json:"exchange"`
AmountIn string `json:"amount_in"`
TokenIn string `json:"token_in"`
TokenOut string `json:"token_out"`
ExpectedProfit string `json:"expected_profit"`
MaxSlippage float64 `json:"max_slippage"`
GasCost string `json:"gas_cost"`
MinROI float64 `json:"min_roi"`
TestType string `json:"test_type"` // "static", "stress", "edge_case"
}
// ExchangeProfitResult holds profitability audit results for an exchange
type ExchangeProfitResult struct {
Exchange string `json:"exchange"`
TotalTests int `json:"total_tests"`
PassedTests int `json:"passed_tests"`
FailedTests int `json:"failed_tests"`
AverageProfitBP float64 `json:"average_profit_bp"`
MaxProfitBP float64 `json:"max_profit_bp"`
MinProfitBP float64 `json:"min_profit_bp"`
AverageROI float64 `json:"average_roi"`
TestResults []*ProfitTestResult `json:"test_results"`
FailedCases []*ProfitTestFailure `json:"failed_cases"`
Duration time.Duration `json:"duration"`
Timestamp time.Time `json:"timestamp"`
}
// ProfitTestResult represents the result of a single profit test
type ProfitTestResult struct {
TestName string `json:"test_name"`
Passed bool `json:"passed"`
ActualProfitBP float64 `json:"actual_profit_bp"`
ExpectedProfitBP float64 `json:"expected_profit_bp"`
ActualROI float64 `json:"actual_roi"`
ExpectedROI float64 `json:"expected_roi"`
SlippageBP float64 `json:"slippage_bp"`
GasCostETH string `json:"gas_cost_eth"`
NetProfitETH string `json:"net_profit_eth"`
Duration time.Duration `json:"duration"`
ErrorMessage string `json:"error_message,omitempty"`
}
// ProfitTestFailure represents a failed profit test
type ProfitTestFailure struct {
TestName string `json:"test_name"`
Reason string `json:"reason"`
Expected float64 `json:"expected"`
Actual float64 `json:"actual"`
Difference float64 `json:"difference"`
Severity string `json:"severity"` // "low", "medium", "high", "critical"
}
// NewProfitabilityAuditor creates a new profitability auditor
func NewProfitabilityAuditor(config *Config) (*ProfitabilityAuditor, error) {
converter := math.NewDecimalConverter()
calculator := math.NewArbitrageCalculator(nil) // TODO: Add proper gas estimator
// Load test scenarios
scenarios, err := loadProfitabilityScenarios(config.ScenariosFile)
if err != nil {
return nil, fmt.Errorf("failed to load scenarios: %w", err)
}
return &ProfitabilityAuditor{
config: config,
converter: converter,
calculator: calculator,
scenarios: scenarios,
results: make(map[string]*ExchangeProfitResult),
}, nil
}
// AuditExchange performs profitability audit for a specific exchange
func (pa *ProfitabilityAuditor) AuditExchange(ctx context.Context, exchange string) error {
startTime := time.Now()
if pa.config.Verbose {
fmt.Printf("Starting profitability audit for %s...\n", exchange)
}
result := &ExchangeProfitResult{
Exchange: exchange,
TestResults: []*ProfitTestResult{},
FailedCases: []*ProfitTestFailure{},
Timestamp: startTime,
}
// Get scenarios for this exchange
exchangeScenarios := pa.getExchangeScenarios(exchange)
result.TotalTests = len(exchangeScenarios)
var totalProfitBP, totalROI float64
maxProfitBP := -1000.0
minProfitBP := 1000.0
for _, scenario := range exchangeScenarios {
testResult, err := pa.runProfitTest(ctx, scenario)
if err != nil {
if pa.config.Verbose {
fmt.Printf(" Failed test %s: %v\n", scenario.Name, err)
}
failure := &ProfitTestFailure{
TestName: scenario.Name,
Reason: err.Error(),
Severity: "high",
}
result.FailedCases = append(result.FailedCases, failure)
result.FailedTests++
continue
}
result.TestResults = append(result.TestResults, testResult)
if testResult.Passed {
result.PassedTests++
totalProfitBP += testResult.ActualProfitBP
totalROI += testResult.ActualROI
if testResult.ActualProfitBP > maxProfitBP {
maxProfitBP = testResult.ActualProfitBP
}
if testResult.ActualProfitBP < minProfitBP {
minProfitBP = testResult.ActualProfitBP
}
} else {
result.FailedTests++
failure := &ProfitTestFailure{
TestName: scenario.Name,
Reason: testResult.ErrorMessage,
Expected: testResult.ExpectedProfitBP,
Actual: testResult.ActualProfitBP,
Difference: testResult.ActualProfitBP - testResult.ExpectedProfitBP,
Severity: pa.calculateSeverity(testResult.ActualProfitBP, testResult.ExpectedProfitBP),
}
result.FailedCases = append(result.FailedCases, failure)
}
if pa.config.Verbose {
status := "✓"
if !testResult.Passed {
status = "✗"
}
fmt.Printf(" %s %s: Profit=%.2f bp, ROI=%.2f%%\n",
status, scenario.Name, testResult.ActualProfitBP, testResult.ActualROI)
}
}
// Calculate averages
if result.PassedTests > 0 {
result.AverageProfitBP = totalProfitBP / float64(result.PassedTests)
result.AverageROI = totalROI / float64(result.PassedTests)
result.MaxProfitBP = maxProfitBP
result.MinProfitBP = minProfitBP
}
result.Duration = time.Since(startTime)
pa.results[exchange] = result
if pa.config.Verbose {
fmt.Printf("Completed %s audit: %d/%d tests passed (%.1f%%)\n",
exchange, result.PassedTests, result.TotalTests,
float64(result.PassedTests)/float64(result.TotalTests)*100)
}
return nil
}
// runProfitTest executes a single profit test
func (pa *ProfitabilityAuditor) runProfitTest(ctx context.Context, scenario *ProfitabilityTest) (*ProfitTestResult, error) {
startTime := time.Now()
// Convert input amounts to UniversalDecimal
amountIn, err := pa.converter.FromString(scenario.AmountIn, 18, scenario.TokenIn)
if err != nil {
return nil, fmt.Errorf("invalid amount_in: %w", err)
}
gasCost, err := pa.converter.FromString(scenario.GasCost, 18, "ETH")
if err != nil {
return nil, fmt.Errorf("invalid gas_cost: %w", err)
}
expectedProfit, err := pa.converter.FromString(scenario.ExpectedProfit, 18, "ETH")
if err != nil {
return nil, fmt.Errorf("invalid expected_profit: %w", err)
}
// Simulate arbitrage calculation
// TODO: Integrate with actual exchange data and calculation engine
actualProfit, netProfit, slippage, err := pa.simulateProfitCalculation(scenario, amountIn, gasCost)
if err != nil {
return nil, fmt.Errorf("profit simulation failed: %w", err)
}
// Calculate metrics
actualProfitBP := pa.calculateProfitBP(actualProfit, amountIn)
expectedProfitBP := pa.calculateProfitBP(expectedProfit, amountIn)
actualROI := actualProfitBP / 100.0 // Convert BP to percentage
// Determine if test passed
profitDiff := actualProfitBP - expectedProfitBP
profitPassed := actualProfitBP >= pa.config.MinProfitBP
slippagePassed := slippage <= pa.config.MaxSlippageBP
roiPassed := actualROI >= scenario.MinROI
passed := profitPassed && slippagePassed && roiPassed
result := &ProfitTestResult{
TestName: scenario.Name,
Passed: passed,
ActualProfitBP: actualProfitBP,
ExpectedProfitBP: expectedProfitBP,
ActualROI: actualROI,
ExpectedROI: scenario.MinROI,
SlippageBP: slippage,
GasCostETH: pa.converter.ToHumanReadable(gasCost),
NetProfitETH: pa.converter.ToHumanReadable(netProfit),
Duration: time.Since(startTime),
}
if !passed {
var reasons []string
if !profitPassed {
reasons = append(reasons, fmt.Sprintf("profit %.2f bp below minimum %.2f bp", actualProfitBP, pa.config.MinProfitBP))
}
if !slippagePassed {
reasons = append(reasons, fmt.Sprintf("slippage %.2f bp exceeds maximum %.2f bp", slippage, pa.config.MaxSlippageBP))
}
if !roiPassed {
reasons = append(reasons, fmt.Sprintf("ROI %.2f%% below minimum %.2f%%", actualROI, scenario.MinROI))
}
result.ErrorMessage = fmt.Sprintf("Test failed: %v", reasons)
}
return result, nil
}
// simulateProfitCalculation simulates profit calculation for a scenario
func (pa *ProfitabilityAuditor) simulateProfitCalculation(scenario *ProfitabilityTest, amountIn, gasCost *math.UniversalDecimal) (*math.UniversalDecimal, *math.UniversalDecimal, float64, error) {
// This is a simplified simulation - in production, this would integrate with real exchange APIs
// Simulate price impact and slippage based on exchange type
var slippageBP float64
var profitMultiplier float64
switch scenario.Exchange {
case "uniswap_v2":
slippageBP = 15.0 // Simulate 15 bp slippage
profitMultiplier = 1.025 // 2.5% profit
case "uniswap_v3":
slippageBP = 8.0 // Lower slippage for V3
profitMultiplier = 1.032 // 3.2% profit
case "curve":
slippageBP = 3.0 // Very low slippage for stable swaps
profitMultiplier = 1.008 // 0.8% profit
case "balancer":
slippageBP = 25.0 // Higher slippage for weighted pools
profitMultiplier = 1.045 // 4.5% profit
default:
return nil, nil, 0, fmt.Errorf("unsupported exchange: %s", scenario.Exchange)
}
// Calculate gross profit
profitAmount := new(big.Int).Mul(amountIn.Value, big.NewInt(int64(profitMultiplier*1000)))
profitAmount.Div(profitAmount, big.NewInt(1000))
profitAmount.Sub(profitAmount, amountIn.Value)
grossProfit, err := math.NewUniversalDecimal(profitAmount, 18, "ETH")
if err != nil {
return nil, nil, 0, err
}
// Calculate net profit (gross - gas)
netProfitAmount := new(big.Int).Sub(grossProfit.Value, gasCost.Value)
netProfit, err := math.NewUniversalDecimal(netProfitAmount, 18, "ETH")
if err != nil {
return nil, nil, 0, err
}
return grossProfit, netProfit, slippageBP, nil
}
// calculateProfitBP calculates profit in basis points
func (pa *ProfitabilityAuditor) calculateProfitBP(profit, amountIn *math.UniversalDecimal) float64 {
if amountIn.Value.Cmp(big.NewInt(0)) == 0 {
return 0.0
}
// Convert to float for calculation
profitFloat, _ := new(big.Float).SetInt(profit.Value).Float64()
amountFloat, _ := new(big.Float).SetInt(amountIn.Value).Float64()
return (profitFloat / amountFloat) * 10000.0 // Convert to basis points
}
// calculateSeverity determines the severity of a test failure
func (pa *ProfitabilityAuditor) calculateSeverity(actual, expected float64) string {
diff := expected - actual
diffPercent := (diff / expected) * 100
switch {
case diffPercent > 50:
return "critical"
case diffPercent > 25:
return "high"
case diffPercent > 10:
return "medium"
default:
return "low"
}
}
// getExchangeScenarios returns test scenarios for a specific exchange
func (pa *ProfitabilityAuditor) getExchangeScenarios(exchange string) []*ProfitabilityTest {
var scenarios []*ProfitabilityTest
for _, scenario := range pa.scenarios.Scenarios {
if scenario.Exchange == exchange || scenario.Exchange == "all" {
scenarios = append(scenarios, scenario)
}
}
// If no scenarios found, create default ones
if len(scenarios) == 0 {
scenarios = pa.createDefaultScenarios(exchange)
}
return scenarios
}
// createDefaultScenarios creates default test scenarios for an exchange
func (pa *ProfitabilityAuditor) createDefaultScenarios(exchange string) []*ProfitabilityTest {
baseScenarios := []*ProfitabilityTest{
{
Name: fmt.Sprintf("%s_small_arbitrage", exchange),
Description: "Small arbitrage opportunity",
Exchange: exchange,
AmountIn: "1000000000000000000", // 1 ETH
TokenIn: "ETH",
TokenOut: "USDC",
ExpectedProfit: "25000000000000000", // 0.025 ETH
MaxSlippage: 50.0,
GasCost: "5000000000000000", // 0.005 ETH
MinROI: 1.0,
TestType: "static",
},
{
Name: fmt.Sprintf("%s_medium_arbitrage", exchange),
Description: "Medium arbitrage opportunity",
Exchange: exchange,
AmountIn: "10000000000000000000", // 10 ETH
TokenIn: "ETH",
TokenOut: "USDC",
ExpectedProfit: "300000000000000000", // 0.3 ETH
MaxSlippage: 100.0,
GasCost: "8000000000000000", // 0.008 ETH
MinROI: 2.5,
TestType: "static",
},
{
Name: fmt.Sprintf("%s_large_arbitrage", exchange),
Description: "Large arbitrage opportunity",
Exchange: exchange,
AmountIn: "100000000000000000000", // 100 ETH
TokenIn: "ETH",
TokenOut: "USDC",
ExpectedProfit: "5000000000000000000", // 5 ETH
MaxSlippage: 200.0,
GasCost: "15000000000000000", // 0.015 ETH
MinROI: 4.5,
TestType: "stress",
},
}
return baseScenarios
}
// MonitorRealTimeProfit monitors real-time profit opportunities
func (pa *ProfitabilityAuditor) MonitorRealTimeProfit(ctx context.Context, exchange string) error {
// TODO: Implement real-time monitoring with live market data
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
fmt.Printf("Monitoring real-time profitability for %s...\n", exchange)
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
// Simulate real-time profit check
if pa.config.Verbose {
fmt.Printf("[%s] Checking real-time opportunities for %s...\n",
time.Now().Format("15:04:05"), exchange)
}
// TODO: Integrate with live market data and opportunity detection
}
}
}
// GenerateReport generates comprehensive profitability audit reports
func (pa *ProfitabilityAuditor) GenerateReport() error {
// Generate JSON report
reportPath := filepath.Join(pa.config.OutputDir, "profitability_audit.json")
if err := pa.generateJSONReport(reportPath); err != nil {
return fmt.Errorf("failed to generate JSON report: %w", err)
}
// Generate Markdown report
markdownPath := filepath.Join(pa.config.OutputDir, "profitability_audit.md")
if err := pa.generateMarkdownReport(markdownPath); err != nil {
return fmt.Errorf("failed to generate Markdown report: %w", err)
}
return nil
}
// generateJSONReport generates a JSON format report
func (pa *ProfitabilityAuditor) generateJSONReport(path string) error {
report := map[string]interface{}{
"timestamp": time.Now(),
"config": pa.config,
"exchange_results": pa.results,
}
data, err := json.MarshalIndent(report, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, data, 0644)
}
// generateMarkdownReport generates a Markdown format report
func (pa *ProfitabilityAuditor) generateMarkdownReport(path string) error {
// TODO: Implement detailed Markdown report generation
content := fmt.Sprintf("# Profitability Audit Report\n\nGenerated: %s\n\n", time.Now().Format(time.RFC3339))
for exchange, result := range pa.results {
content += fmt.Sprintf("## %s\n\n", exchange)
content += fmt.Sprintf("- Total Tests: %d\n", result.TotalTests)
content += fmt.Sprintf("- Passed: %d\n", result.PassedTests)
content += fmt.Sprintf("- Failed: %d\n", result.FailedTests)
content += fmt.Sprintf("- Average Profit: %.2f bp\n", result.AverageProfitBP)
content += fmt.Sprintf("- Average ROI: %.2f%%\n\n", result.AverageROI)
}
return os.WriteFile(path, []byte(content), 0644)
}
// loadProfitabilityScenarios loads test scenarios from file
func loadProfitabilityScenarios(filename string) (*ProfitabilityScenarios, error) {
// If filename is "default", create default scenarios
if filename == "default" {
return &ProfitabilityScenarios{
Version: "1.0.0",
Scenarios: make(map[string]*ProfitabilityTest),
}, nil
}
// TODO: Load from actual file
return &ProfitabilityScenarios{
Version: "1.0.0",
Scenarios: make(map[string]*ProfitabilityTest),
}, nil
}