class ProtocolAnalyticsTracker {
constructor() {
this.history = [];
this.loadFromStorage();
}
async recordSnapshot() {
try {
const metrics = await getProtocolMetrics();
const snapshot = {
timestamp: Date.now(),
date: new Date().toISOString(),
...metrics
};
this.history.push(snapshot);
// Keep only last 90 days of data
const ninetyDaysAgo = Date.now() - (90 * 24 * 60 * 60 * 1000);
this.history = this.history.filter(s => s.timestamp > ninetyDaysAgo);
this.saveToStorage();
return snapshot;
} catch (error) {
console.error('Failed to record analytics snapshot:', error);
return null;
}
}
getGrowthMetrics(days = 30) {
if (this.history.length < 2) {
return { message: "Insufficient historical data" };
}
const cutoff = Date.now() - (days * 24 * 60 * 60 * 1000);
const recentData = this.history
.filter(s => s.timestamp > cutoff)
.sort((a, b) => a.timestamp - b.timestamp);
if (recentData.length < 2) {
return { message: `Insufficient data for ${days}-day analysis` };
}
const first = recentData[0];
const last = recentData[recentData.length - 1];
const timeSpan = (last.timestamp - first.timestamp) / (1000 * 60 * 60 * 24);
const volumeGrowth = last.volume.total - first.volume.total;
const feesGrowth = last.fees.total - first.fees.total;
const treasuryGrowth = last.treasury.balance - first.treasury.balance;
const usersGrowth = last.users.active - first.users.active;
const positionsGrowth = last.positions.total - first.positions.total;
return {
period: `${days} days`,
timeSpan: timeSpan.toFixed(1),
growth: {
volume: {
absolute: volumeGrowth,
percentage: ((volumeGrowth / first.volume.total) * 100).toFixed(2),
daily: (volumeGrowth / timeSpan).toFixed(2)
},
fees: {
absolute: feesGrowth,
percentage: ((feesGrowth / first.fees.total) * 100).toFixed(2),
daily: (feesGrowth / timeSpan).toFixed(2)
},
treasury: {
absolute: treasuryGrowth,
percentage: ((treasuryGrowth / first.treasury.balance) * 100).toFixed(2),
daily: (treasuryGrowth / timeSpan).toFixed(2)
},
users: {
absolute: usersGrowth,
percentage: ((usersGrowth / first.users.active) * 100).toFixed(2),
daily: (usersGrowth / timeSpan).toFixed(2)
},
positions: {
absolute: positionsGrowth,
percentage: ((positionsGrowth / first.positions.total) * 100).toFixed(2),
daily: (positionsGrowth / timeSpan).toFixed(2)
}
}
};
}
calculateKPIs() {
if (this.history.length === 0) {
return { message: "No historical data available" };
}
const latest = this.history[this.history.length - 1];
// Calculate key performance indicators
const avgVolumePerUser = latest.volume.total / latest.users.active;
const avgPositionsPerUser = latest.positions.total / latest.users.active;
const avgVolumePerPosition = latest.volume.total / latest.positions.total;
const feeRate = (latest.fees.total / latest.volume.total) * 100;
const treasuryRatio = (latest.treasury.balance / latest.volume.total) * 100;
return {
timestamp: latest.date,
kpis: {
avgVolumePerUser: avgVolumePerUser.toFixed(2),
avgPositionsPerUser: avgPositionsPerUser.toFixed(2),
avgVolumePerPosition: avgVolumePerPosition.toFixed(2),
feeRate: feeRate.toFixed(3) + '%',
treasuryRatio: treasuryRatio.toFixed(2) + '%'
},
efficiency: this.assessEfficiency(latest),
health: this.assessProtocolHealth(latest)
};
}
assessEfficiency(metrics) {
const scores = [];
// Volume per user efficiency
const avgVolumePerUser = metrics.volume.total / metrics.users.active;
if (avgVolumePerUser > 100000) scores.push({ metric: 'High volume per user', score: 25 });
else if (avgVolumePerUser > 50000) scores.push({ metric: 'Good volume per user', score: 15 });
else scores.push({ metric: 'Low volume per user', score: 5 });
// Fee efficiency
const feeRate = (metrics.fees.total / metrics.volume.total) * 100;
if (feeRate > 0.5) scores.push({ metric: 'High fee collection rate', score: 25 });
else if (feeRate > 0.2) scores.push({ metric: 'Good fee collection rate', score: 15 });
else scores.push({ metric: 'Low fee collection rate', score: 5 });
// Treasury management
const treasuryRatio = (metrics.treasury.balance / metrics.volume.total) * 100;
if (treasuryRatio > 2) scores.push({ metric: 'Strong treasury reserves', score: 25 });
else if (treasuryRatio > 1) scores.push({ metric: 'Adequate treasury reserves', score: 15 });
else scores.push({ metric: 'Low treasury reserves', score: 5 });
// User engagement
const positionsPerUser = metrics.positions.total / metrics.users.active;
if (positionsPerUser > 5) scores.push({ metric: 'High user engagement', score: 25 });
else if (positionsPerUser > 2) scores.push({ metric: 'Good user engagement', score: 15 });
else scores.push({ metric: 'Low user engagement', score: 5 });
const totalScore = scores.reduce((sum, s) => sum + s.score, 0);
let rating = 'POOR';
if (totalScore >= 80) rating = 'EXCELLENT';
else if (totalScore >= 60) rating = 'GOOD';
else if (totalScore >= 40) rating = 'FAIR';
return {
totalScore,
rating,
factors: scores
};
}
assessProtocolHealth(metrics) {
const healthFactors = [];
let healthScore = 0;
// Treasury health
const treasuryRatio = (metrics.treasury.balance / metrics.volume.total) * 100;
if (treasuryRatio > 2) {
healthFactors.push({ factor: 'Treasury Health', status: 'EXCELLENT', impact: 30 });
healthScore += 30;
} else if (treasuryRatio > 1) {
healthFactors.push({ factor: 'Treasury Health', status: 'GOOD', impact: 20 });
healthScore += 20;
} else {
healthFactors.push({ factor: 'Treasury Health', status: 'CONCERNING', impact: 10 });
healthScore += 10;
}
// User growth
if (metrics.users.active > 1000) {
healthFactors.push({ factor: 'User Base', status: 'STRONG', impact: 25 });
healthScore += 25;
} else if (metrics.users.active > 500) {
healthFactors.push({ factor: 'User Base', status: 'GROWING', impact: 15 });
healthScore += 15;
} else {
healthFactors.push({ factor: 'User Base', status: 'SMALL', impact: 5 });
healthScore += 5;
}
// Volume activity
if (metrics.volume.total > 100000000) { // 100M USD+
healthFactors.push({ factor: 'Trading Activity', status: 'HIGH', impact: 25 });
healthScore += 25;
} else if (metrics.volume.total > 10000000) { // 10M USD+
healthFactors.push({ factor: 'Trading Activity', status: 'MODERATE', impact: 15 });
healthScore += 15;
} else {
healthFactors.push({ factor: 'Trading Activity', status: 'LOW', impact: 5 });
healthScore += 5;
}
// Fee generation
const feeRate = (metrics.fees.total / metrics.volume.total) * 100;
if (feeRate > 0.3) {
healthFactors.push({ factor: 'Revenue Generation', status: 'STRONG', impact: 20 });
healthScore += 20;
} else if (feeRate > 0.1) {
healthFactors.push({ factor: 'Revenue Generation', status: 'ADEQUATE', impact: 10 });
healthScore += 10;
} else {
healthFactors.push({ factor: 'Revenue Generation', status: 'WEAK', impact: 0 });
}
let overallHealth = 'CRITICAL';
if (healthScore >= 80) overallHealth = 'EXCELLENT';
else if (healthScore >= 60) overallHealth = 'GOOD';
else if (healthScore >= 40) overallHealth = 'FAIR';
else if (healthScore >= 20) overallHealth = 'POOR';
return {
overallHealth,
healthScore,
factors: healthFactors
};
}
generateReport() {
if (this.history.length === 0) {
return { error: "No analytics data available" };
}
const latest = this.history[this.history.length - 1];
const growth30d = this.getGrowthMetrics(30);
const growth7d = this.getGrowthMetrics(7);
const kpis = this.calculateKPIs();
return {
snapshot: {
timestamp: latest.date,
metrics: latest
},
growth: {
last30Days: growth30d,
last7Days: growth7d
},
performance: kpis,
insights: this.generateInsights(),
recommendations: this.generateRecommendations()
};
}
generateInsights() {
const insights = [];
if (this.history.length < 2) {
return [{ type: 'INFO', message: 'Insufficient data for insights generation' }];
}
const latest = this.history[this.history.length - 1];
const growth7d = this.getGrowthMetrics(7);
// Volume insights
if (parseFloat(growth7d.growth?.volume?.percentage || 0) > 20) {
insights.push({
type: 'POSITIVE',
category: 'Volume',
message: `Trading volume increased by ${growth7d.growth.volume.percentage}% in the last week`
});
}
// User insights
if (parseFloat(growth7d.growth?.users?.percentage || 0) > 10) {
insights.push({
type: 'POSITIVE',
category: 'Users',
message: `Active users increased by ${growth7d.growth.users.percentage}% in the last week`
});
}
// Treasury insights
const treasuryRatio = (latest.treasury.balance / latest.volume.total) * 100;
if (treasuryRatio < 1) {
insights.push({
type: 'WARNING',
category: 'Treasury',
message: 'Treasury balance is low relative to trading volume - monitor payout capacity'
});
}
return insights;
}
generateRecommendations() {
const recommendations = [];
const latest = this.history[this.history.length - 1];
// Treasury recommendations
const treasuryRatio = (latest.treasury.balance / latest.volume.total) * 100;
if (treasuryRatio < 1) {
recommendations.push({
priority: 'HIGH',
category: 'Treasury Management',
action: 'Increase treasury reserves',
reason: 'Low treasury balance may impact payout capabilities'
});
}
// User growth recommendations
if (latest.users.active < 1000) {
recommendations.push({
priority: 'MEDIUM',
category: 'User Acquisition',
action: 'Focus on user growth strategies',
reason: 'Expanding user base will drive volume and fee growth'
});
}
// Volume recommendations
const avgVolumePerUser = latest.volume.total / latest.users.active;
if (avgVolumePerUser < 50000) {
recommendations.push({
priority: 'MEDIUM',
category: 'User Engagement',
action: 'Improve user retention and trading frequency',
reason: 'Low average volume per user indicates room for engagement improvement'
});
}
return recommendations;
}
saveToStorage() {
if (typeof localStorage !== 'undefined') {
localStorage.setItem('protocol_analytics_history', JSON.stringify(this.history));
}
}
loadFromStorage() {
if (typeof localStorage !== 'undefined') {
const stored = localStorage.getItem('protocol_analytics_history');
if (stored) {
this.history = JSON.parse(stored);
}
}
}
}
// Usage
const tracker = new ProtocolAnalyticsTracker();
// Record snapshots regularly
setInterval(() => {
tracker.recordSnapshot();
}, 6 * 60 * 60 * 1000); // Every 6 hours
// Generate comprehensive report
const report = tracker.generateReport();
console.log('Protocol Analytics Report:', report);