Files
mev-beta/orig/pkg/execution/alerts.go
Administrator 803de231ba feat: create v2-prep branch with comprehensive planning
Restructured project for V2 refactor:

**Structure Changes:**
- Moved all V1 code to orig/ folder (preserved with git mv)
- Created docs/planning/ directory
- Added orig/README_V1.md explaining V1 preservation

**Planning Documents:**
- 00_V2_MASTER_PLAN.md: Complete architecture overview
  - Executive summary of critical V1 issues
  - High-level component architecture diagrams
  - 5-phase implementation roadmap
  - Success metrics and risk mitigation

- 07_TASK_BREAKDOWN.md: Atomic task breakdown
  - 99+ hours of detailed tasks
  - Every task < 2 hours (atomic)
  - Clear dependencies and success criteria
  - Organized by implementation phase

**V2 Key Improvements:**
- Per-exchange parsers (factory pattern)
- Multi-layer strict validation
- Multi-index pool cache
- Background validation pipeline
- Comprehensive observability

**Critical Issues Addressed:**
- Zero address tokens (strict validation + cache enrichment)
- Parsing accuracy (protocol-specific parsers)
- No audit trail (background validation channel)
- Inefficient lookups (multi-index cache)
- Stats disconnection (event-driven metrics)

Next Steps:
1. Review planning documents
2. Begin Phase 1: Foundation (P1-001 through P1-010)
3. Implement parsers in Phase 2
4. Build cache system in Phase 3
5. Add validation pipeline in Phase 4
6. Migrate and test in Phase 5

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

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

292 lines
6.9 KiB
Go
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package execution
import (
"fmt"
"math/big"
"time"
"github.com/fraktal/mev-beta/internal/logger"
"github.com/fraktal/mev-beta/pkg/types"
)
// AlertLevel defines the severity of an alert
type AlertLevel int
const (
InfoLevel AlertLevel = iota
WarningLevel
CriticalLevel
)
func (al AlertLevel) String() string {
switch al {
case InfoLevel:
return "INFO"
case WarningLevel:
return "WARNING"
case CriticalLevel:
return "CRITICAL"
default:
return "UNKNOWN"
}
}
// Alert represents a system alert
type Alert struct {
Level AlertLevel
Title string
Message string
Opportunity *types.ArbitrageOpportunity
Timestamp time.Time
}
// AlertConfig holds configuration for the alert system
type AlertConfig struct {
EnableConsoleAlerts bool
EnableFileAlerts bool
EnableWebhook bool
WebhookURL string
MinProfitForAlert *big.Int // Minimum profit to trigger alert (wei)
MinROIForAlert float64 // Minimum ROI to trigger alert (0.05 = 5%)
AlertCooldown time.Duration // Minimum time between alerts
}
// AlertSystem handles opportunity alerts and notifications
type AlertSystem struct {
config *AlertConfig
logger *logger.Logger
lastAlertTime time.Time
alertCount uint64
}
// NewAlertSystem creates a new alert system
func NewAlertSystem(config *AlertConfig, logger *logger.Logger) *AlertSystem {
return &AlertSystem{
config: config,
logger: logger,
lastAlertTime: time.Time{},
alertCount: 0,
}
}
// SendOpportunityAlert sends an alert for a profitable opportunity
func (as *AlertSystem) SendOpportunityAlert(opp *types.ArbitrageOpportunity) {
// Check cooldown
if time.Since(as.lastAlertTime) < as.config.AlertCooldown {
as.logger.Debug("Alert cooldown active, skipping alert")
return
}
// Check minimum thresholds
if opp.NetProfit.Cmp(as.config.MinProfitForAlert) < 0 {
return
}
if opp.ROI < as.config.MinROIForAlert {
return
}
// Determine alert level
level := as.determineAlertLevel(opp)
// Create alert
alert := &Alert{
Level: level,
Title: fmt.Sprintf("Profitable Arbitrage Opportunity Detected"),
Message: as.formatOpportunityMessage(opp),
Opportunity: opp,
Timestamp: time.Now(),
}
// Send alert via configured channels
as.sendAlert(alert)
as.lastAlertTime = time.Now()
as.alertCount++
}
// SendExecutionAlert sends an alert for execution results
func (as *AlertSystem) SendExecutionAlert(result *ExecutionResult) {
var level AlertLevel
var title string
if result.Success {
level = InfoLevel
title = "Arbitrage Executed Successfully"
} else {
level = WarningLevel
title = "Arbitrage Execution Failed"
}
alert := &Alert{
Level: level,
Title: title,
Message: as.formatExecutionMessage(result),
Timestamp: time.Now(),
}
as.sendAlert(alert)
}
// SendSystemAlert sends a system-level alert
func (as *AlertSystem) SendSystemAlert(level AlertLevel, title, message string) {
alert := &Alert{
Level: level,
Title: title,
Message: message,
Timestamp: time.Now(),
}
as.sendAlert(alert)
}
// determineAlertLevel determines the appropriate alert level
func (as *AlertSystem) determineAlertLevel(opp *types.ArbitrageOpportunity) AlertLevel {
// Critical if ROI > 10% or profit > 1 ETH
oneETH := new(big.Int).Mul(big.NewInt(1), big.NewInt(1e18))
if opp.ROI > 0.10 || opp.NetProfit.Cmp(oneETH) > 0 {
return CriticalLevel
}
// Warning if ROI > 5% or profit > 0.1 ETH
pointOneETH := new(big.Int).Mul(big.NewInt(1), big.NewInt(1e17))
if opp.ROI > 0.05 || opp.NetProfit.Cmp(pointOneETH) > 0 {
return WarningLevel
}
return InfoLevel
}
// sendAlert sends an alert via all configured channels
func (as *AlertSystem) sendAlert(alert *Alert) {
// Console alert
if as.config.EnableConsoleAlerts {
as.sendConsoleAlert(alert)
}
// File alert
if as.config.EnableFileAlerts {
as.sendFileAlert(alert)
}
// Webhook alert
if as.config.EnableWebhook && as.config.WebhookURL != "" {
as.sendWebhookAlert(alert)
}
}
// sendConsoleAlert prints alert to console
func (as *AlertSystem) sendConsoleAlert(alert *Alert) {
emoji := ""
switch alert.Level {
case WarningLevel:
emoji = "⚠️"
case CriticalLevel:
emoji = "🚨"
}
as.logger.Info(fmt.Sprintf("%s [%s] %s", emoji, alert.Level, alert.Title))
as.logger.Info(alert.Message)
}
// sendFileAlert writes alert to file
func (as *AlertSystem) sendFileAlert(alert *Alert) {
// TODO: Implement file-based alerts
// Write to logs/alerts/alert_YYYYMMDD_HHMMSS.json
}
// sendWebhookAlert sends alert to webhook (Slack, Discord, etc.)
func (as *AlertSystem) sendWebhookAlert(alert *Alert) {
// TODO: Implement webhook alerts
// POST JSON to configured webhook URL
as.logger.Debug(fmt.Sprintf("Would send webhook alert to: %s", as.config.WebhookURL))
}
// formatOpportunityMessage formats an opportunity alert message
func (as *AlertSystem) formatOpportunityMessage(opp *types.ArbitrageOpportunity) string {
profitETH := new(big.Float).Quo(
new(big.Float).SetInt(opp.NetProfit),
big.NewFloat(1e18),
)
gasEstimate := "N/A"
if opp.GasEstimate != nil {
gasEstimate = opp.GasEstimate.String()
}
return fmt.Sprintf(`
🎯 Arbitrage Opportunity Details:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• ID: %s
• Path: %v
• Protocol: %s
• Amount In: %s wei
• Estimated Profit: %.6f ETH
• ROI: %.2f%%
• Gas Estimate: %s wei
• Confidence: %.1f%%
• Price Impact: %.2f%%
• Expires: %s
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
`,
opp.ID,
opp.Path,
opp.Protocol,
opp.AmountIn.String(),
profitETH,
opp.ROI*100,
gasEstimate,
opp.Confidence*100,
opp.PriceImpact*100,
opp.ExpiresAt.Format("15:04:05"),
)
}
// formatExecutionMessage formats an execution result message
func (as *AlertSystem) formatExecutionMessage(result *ExecutionResult) string {
status := "✅ SUCCESS"
if !result.Success {
status = "❌ FAILED"
}
profitETH := "N/A"
if result.ActualProfit != nil {
p := new(big.Float).Quo(
new(big.Float).SetInt(result.ActualProfit),
big.NewFloat(1e18),
)
profitETH = fmt.Sprintf("%.6f ETH", p)
}
errorMsg := ""
if result.Error != nil {
errorMsg = fmt.Sprintf("\n• Error: %v", result.Error)
}
return fmt.Sprintf(`
%s Arbitrage Execution
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• Opportunity ID: %s
• Tx Hash: %s
• Actual Profit: %s
• Gas Used: %d
• Slippage: %.2f%%
• Execution Time: %v%s
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
`,
status,
result.OpportunityID,
result.TxHash.Hex(),
profitETH,
result.GasUsed,
result.SlippagePercent*100,
result.ExecutionTime,
errorMsg,
)
}
// GetAlertCount returns the total number of alerts sent
func (as *AlertSystem) GetAlertCount() uint64 {
return as.alertCount
}