- Added comprehensive bounds checking to prevent buffer overruns in multicall parsing - Implemented graduated validation system (Strict/Moderate/Permissive) to reduce false positives - Added LRU caching system for address validation with 10-minute TTL - Enhanced ABI decoder with missing Universal Router and Arbitrum-specific DEX signatures - Fixed duplicate function declarations and import conflicts across multiple files - Added error recovery mechanisms with multiple fallback strategies - Updated tests to handle new validation behavior for suspicious addresses - Fixed parser test expectations for improved validation system - Applied gofmt formatting fixes to ensure code style compliance - Fixed mutex copying issues in monitoring package by introducing MetricsSnapshot - Resolved critical security vulnerabilities in heuristic address extraction - Progress: Updated TODO audit from 10% to 35% complete 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
588 lines
19 KiB
Go
588 lines
19 KiB
Go
package arbitrum
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/fraktal/mev-beta/internal/logger"
|
|
"github.com/fraktal/mev-beta/pkg/security"
|
|
)
|
|
|
|
// 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)
|
|
}
|
|
// Calculate average profit per successful trade
|
|
if stats.SuccessfulTrades > 0 {
|
|
successfulTradesInt64, err := security.SafeUint64ToInt64(stats.SuccessfulTrades)
|
|
if err != nil {
|
|
pt.logger.Error("Successful trades count exceeds int64 maximum", "successfulTrades", stats.SuccessfulTrades, "error", err)
|
|
// Use maximum safe value as fallback to avoid division by zero
|
|
successfulTradesInt64 = math.MaxInt64
|
|
}
|
|
avgProfit := new(big.Int).Div(profit, big.NewInt(successfulTradesInt64))
|
|
stats.AverageProfit = avgProfit.String()
|
|
} else {
|
|
// If no successful trades, average profit is 0
|
|
stats.AverageProfit = "0"
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|