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>
This commit is contained in:
587
orig/pkg/arbitrum/profitability_tracker.go
Normal file
587
orig/pkg/arbitrum/profitability_tracker.go
Normal file
@@ -0,0 +1,587 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user