
Analytics
- 13 installs
- 610 repo stars
- Updated June 26, 2026
- alsk1992/cloddsbot
Analytics is a Claude Code skill that measures trading performance with P&L attribution by edge source, platform, time, and strategy, and exports PDF/CSV reports.
About
Analytics is a skill that analyzes trading performance for the clodds bot. It attributes profit and loss by edge source, platform, market category, strategy, and time of day, reports execution quality and edge decay, and exports PDF or CSV reports. A developer uses it to understand which edges and conditions actually drive returns.
- Attributes P&L by edge source, platform, category, and strategy
- Analyzes performance by hour, day, edge size, and liquidity, plus edge decay
- Exports PDF and CSV reports of trading performance
Analytics by the numbers
- 13 all-time installs (skills.sh)
- Ranked #759 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
analytics capabilities & compatibility
- Capabilities
- performance attribution · trade analytics · report export
- Use cases
- data analysis · trading
- Runs
- Runs locally
What analytics says it does
Analyze trading performance with attribution by edge source, time-of-day analysis, and optimization insights.
/analytics attribution P&L by edge source
npx skills add https://github.com/alsk1992/cloddsbot --skill analyticsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 13 |
|---|---|
| repo stars | ★ 610 |
| Last updated | June 26, 2026 |
| Repository | alsk1992/cloddsbot ↗ |
What it does
Attribute trading P&L by edge source, platform, time, and strategy, and export performance reports.
Who is it for?
understanding which edges and conditions drive trading returns
Skip if: placing trades or setting alerts (this only analyzes past performance)
When should I use this skill?
you want to attribute trading P&L or review strategy performance
What you get
A performance breakdown attributing P&L to edge source, platform, strategy, and time, exportable as PDF or CSV.
- P&L attribution breakdown
- PDF performance report
- CSV trade export
By the numbers
- 6 attribution categories
Files
Analytics - Complete API Reference
Analyze trading performance with attribution by edge source, time-of-day analysis, and optimization insights.
---
Chat Commands
Performance Overview
/analytics Performance summary
/analytics today Today's performance
/analytics week Weekly breakdown
/analytics month Monthly breakdownAttribution
/analytics attribution P&L by edge source
/analytics by-platform P&L by platform
/analytics by-category P&L by market category
/analytics by-strategy P&L by strategyTime Analysis
/analytics best-times Best trading hours
/analytics by-hour Hourly performance
/analytics by-day Day of week analysisEdge Analysis
/analytics edge-decay How edge decays over time
/analytics edge-buckets Performance by edge size
/analytics liquidity Performance by liquidity---
TypeScript API Reference
Create Analytics Service
import { createAnalyticsService } from 'clodds/analytics';
const analytics = createAnalyticsService({
// Data source
tradesDb: './trades.db',
// Time zone
timezone: 'America/New_York',
});Performance Summary
const summary = await analytics.getSummary({
period: 'month',
// or: from: '2024-01-01', to: '2024-01-31'
});
console.log('=== Performance ===');
console.log(`Total P&L: $${summary.totalPnl}`);
console.log(`Win Rate: ${summary.winRate}%`);
console.log(`Profit Factor: ${summary.profitFactor}`);
console.log(`Sharpe Ratio: ${summary.sharpeRatio}`);
console.log(`Total Trades: ${summary.totalTrades}`);
console.log(`Avg Trade: $${summary.avgTrade}`);
console.log(`Best Trade: $${summary.bestTrade}`);
console.log(`Worst Trade: $${summary.worstTrade}`);Attribution by Edge Source
const attribution = await analytics.getAttribution('edgeSource');
for (const source of attribution) {
console.log(`${source.name}:`);
console.log(` P&L: $${source.pnl}`);
console.log(` Trades: ${source.trades}`);
console.log(` Win Rate: ${source.winRate}%`);
console.log(` Contribution: ${source.contribution}%`);
}
// Example sources:
// - price_lag (stale prices)
// - liquidity_gap (thin orderbooks)
// - information (news/events)
// - model_edge (external models)
// - combinatorial (arbitrage)Time-of-Day Analysis
const hourly = await analytics.getHourlyPerformance();
console.log('Best Hours:');
for (const hour of hourly.slice(0, 3)) {
console.log(` ${hour.hour}:00 - Win: ${hour.winRate}%, Avg: $${hour.avgPnl}`);
}
console.log('Worst Hours:');
for (const hour of hourly.slice(-3)) {
console.log(` ${hour.hour}:00 - Win: ${hour.winRate}%, Avg: $${hour.avgPnl}`);
}Day-of-Week Analysis
const daily = await analytics.getDayOfWeekPerformance();
for (const day of daily) {
console.log(`${day.name}: $${day.pnl} (${day.trades} trades, ${day.winRate}% win)`);
}Edge Decay Analysis
const decay = await analytics.getEdgeDecay();
console.log('Edge Decay (how fast edge disappears):');
for (const bucket of decay) {
console.log(` ${bucket.holdTime}: ${bucket.avgReturn}% return`);
}
// Shows optimal hold time before edge decaysEdge Size Buckets
const edgeBuckets = await analytics.getEdgeBuckets();
for (const bucket of edgeBuckets) {
console.log(`Edge ${bucket.min}-${bucket.max}%:`);
console.log(` Trades: ${bucket.trades}`);
console.log(` Win Rate: ${bucket.winRate}%`);
console.log(` Avg P&L: $${bucket.avgPnl}`);
console.log(` Realized Edge: ${bucket.realizedEdge}%`);
}Liquidity Analysis
const liquidity = await analytics.getLiquidityAnalysis();
for (const bucket of liquidity) {
console.log(`${bucket.name} liquidity:`);
console.log(` Trades: ${bucket.trades}`);
console.log(` Avg Slippage: ${bucket.avgSlippage}%`);
console.log(` Fill Rate: ${bucket.fillRate}%`);
console.log(` Avg P&L: $${bucket.avgPnl}`);
}Execution Quality
const execution = await analytics.getExecutionQuality();
console.log('=== Execution Quality ===');
console.log(`Avg Slippage: ${execution.avgSlippage}%`);
console.log(`Fill Rate: ${execution.fillRate}%`);
console.log(`Avg Fill Time: ${execution.avgFillTimeMs}ms`);
console.log(`Partial Fills: ${execution.partialFillRate}%`);
console.log(`Rejected Orders: ${execution.rejectionRate}%`);Platform Comparison
const platforms = await analytics.getPlatformComparison();
for (const platform of platforms) {
console.log(`${platform.name}:`);
console.log(` P&L: $${platform.pnl}`);
console.log(` Win Rate: ${platform.winRate}%`);
console.log(` Avg Slippage: ${platform.avgSlippage}%`);
console.log(` Best For: ${platform.strengths.join(', ')}`);
}Export Report
// Generate PDF report
await analytics.exportReport({
format: 'pdf',
period: 'month',
include: ['summary', 'attribution', 'charts'],
outputPath: './reports/january-2024.pdf',
});
// Export raw data
await analytics.exportData({
format: 'csv',
period: 'month',
outputPath: './data/january-trades.csv',
});---
Attribution Categories
| Category | Description |
|---|---|
| Edge Source | Where the edge came from |
| Platform | Which platform traded on |
| Category | Market category (politics, crypto) |
| Strategy | Which strategy generated trade |
| Time | Hour/day of trade |
| Size | Trade size bucket |
---
Key Metrics
| Metric | Good Value | Description |
|---|---|---|
| Win Rate | > 50% | Percent of winning trades |
| Profit Factor | > 1.5 | Gross profit / gross loss |
| Sharpe Ratio | > 1.0 | Risk-adjusted returns |
| Realized Edge | > 0 | Actual vs expected edge |
| Fill Rate | > 95% | Orders fully filled |
---
Best Practices
1. Review weekly — Catch problems early 2. Track attribution — Know where profits come from 3. Optimize timing — Trade your best hours 4. Monitor edge decay — Don't hold too long 5. Check execution — Slippage kills edge
/**
* Analytics CLI Skill
*
* Commands:
* /analytics - View opportunity analytics summary
* /analytics stats [--period Nd] - Performance statistics
* /analytics platforms - Platform pair performance
* /analytics opportunities [--type X] - Browse opportunities
*/
import type { Database } from '../../../db/index';
async function execute(args: string): Promise<string> {
const parts = args.trim().split(/\s+/);
const cmd = parts[0]?.toLowerCase() || 'summary';
try {
const { createOpportunityAnalytics } = await import('../../../opportunity/analytics');
const { createDatabase } = await import('../../../db/index');
const db: Database = createDatabase();
const analytics = createOpportunityAnalytics(db);
switch (cmd) {
case 'summary':
case 'stats': {
const periodFlag = parts.find((p: string) => p.match(/^\d+d$/));
const days = periodFlag ? parseInt(periodFlag, 10) : 30;
const stats = analytics.getStats({ days });
let output = `**Opportunity Analytics** (${days}d)\n\n`;
output += `Total found: ${stats.totalFound}\n`;
output += `Taken: ${stats.taken}\n`;
output += `Win rate: ${stats.winRate.toFixed(1)}%\n`;
output += `Total profit: $${stats.totalProfit.toLocaleString()}\n`;
output += `Avg edge: ${stats.avgEdge.toFixed(2)}%\n`;
return output;
}
case 'today': {
const stats = analytics.getStats({ days: 1 });
let output = `**Today's Performance**\n\n`;
output += `Opportunities found: ${stats.totalFound}\n`;
output += `Taken: ${stats.taken}\n`;
output += `Win rate: ${stats.winRate.toFixed(1)}%\n`;
output += `Profit: $${stats.totalProfit.toLocaleString()}\n`;
output += `Avg edge: ${stats.avgEdge.toFixed(2)}%\n`;
if (stats.bestPlatformPair) {
output += `\nBest pair: ${stats.bestPlatformPair.platforms.join(' <-> ')} (${stats.bestPlatformPair.winRate.toFixed(0)}% WR)\n`;
}
return output;
}
case 'week': {
const stats = analytics.getStats({ days: 7 });
let output = `**Weekly Performance** (7d)\n\n`;
output += `Opportunities found: ${stats.totalFound}\n`;
output += `Taken: ${stats.taken}\n`;
output += `Win rate: ${stats.winRate.toFixed(1)}%\n`;
output += `Profit: $${stats.totalProfit.toLocaleString()}\n`;
output += `Avg edge: ${stats.avgEdge.toFixed(2)}%\n`;
if (Object.keys(stats.byType).length > 0) {
output += `\n**By Type:**\n`;
for (const [type, data] of Object.entries(stats.byType)) {
output += ` ${type}: ${data.count} opps, ${data.winRate.toFixed(0)}% WR, $${data.profit.toFixed(2)}\n`;
}
}
return output;
}
case 'month': {
const stats = analytics.getStats({ days: 30 });
let output = `**Monthly Performance** (30d)\n\n`;
output += `Opportunities found: ${stats.totalFound}\n`;
output += `Taken: ${stats.taken}\n`;
output += `Win rate: ${stats.winRate.toFixed(1)}%\n`;
output += `Profit: $${stats.totalProfit.toLocaleString()}\n`;
output += `Avg edge: ${stats.avgEdge.toFixed(2)}%\n`;
output += `Avg score: ${stats.avgScore.toFixed(1)}\n`;
if (Object.keys(stats.byType).length > 0) {
output += `\n**By Type:**\n`;
for (const [type, data] of Object.entries(stats.byType)) {
output += ` ${type}: ${data.count} opps, ${data.winRate.toFixed(0)}% WR, $${data.profit.toFixed(2)}, avg edge ${data.avgEdge.toFixed(2)}%\n`;
}
}
if (stats.bestPlatformPair) {
output += `\nBest pair: ${stats.bestPlatformPair.platforms.join(' <-> ')} (${stats.bestPlatformPair.count} opps, ${stats.bestPlatformPair.winRate.toFixed(0)}% WR)\n`;
}
return output;
}
case 'attribution': {
const periodFlag = parts.find((p: string) => p.match(/^\d+d$/));
const days = periodFlag ? parseInt(periodFlag, 10) : 30;
const attr = analytics.getPerformanceAttribution({ days });
let output = `**Performance Attribution** (${days}d)\n\n`;
output += `**By Edge Source:**\n`;
for (const [source, bucket] of Object.entries(attr.byEdgeSource)) {
if (bucket.count === 0) continue;
output += ` ${source}: ${bucket.count} opps, ${bucket.winRate.toFixed(0)}% WR, $${bucket.totalPnL.toFixed(2)} PnL, avg edge ${bucket.avgEdge.toFixed(2)}%\n`;
}
output += `\n**Execution Quality:**\n`;
output += ` Avg slippage: ${attr.executionQuality.avgSlippagePct.toFixed(2)}%\n`;
output += ` Avg execution: ${attr.executionQuality.avgExecutionTimeMs.toFixed(0)}ms\n`;
output += ` Fill rate: ${attr.executionQuality.fillRatePct.toFixed(0)}%\n`;
output += ` Partial fills: ${attr.executionQuality.partialFills}\n`;
return output;
}
case 'by-platform': {
const pairs = analytics.getPlatformPairs();
const stats = analytics.getStats({ days: 30 });
let output = '**Performance by Platform**\n\n';
if (Object.keys(stats.byPlatform).length > 0) {
for (const [platform, data] of Object.entries(stats.byPlatform)) {
output += ` ${platform}: ${data.count} opps, ${data.winRate.toFixed(0)}% WR, $${data.profit.toFixed(2)}\n`;
}
}
if (pairs.length > 0) {
output += `\n**Platform Pairs:**\n`;
for (const p of pairs) {
output += ` ${p.platforms.join(' <-> ')}: ${p.count} opps, ${p.winRate.toFixed(0)}% WR, $${p.totalProfit.toFixed(2)} profit, avg edge ${p.avgEdge.toFixed(2)}%\n`;
}
}
if (Object.keys(stats.byPlatform).length === 0 && pairs.length === 0) {
output += 'No platform data yet.\n';
}
return output;
}
case 'by-category': {
const stats = analytics.getStats({ days: 30 });
let output = '**Performance by Category (Type)**\n\n';
if (Object.keys(stats.byType).length === 0) return 'No category data yet.';
for (const [type, data] of Object.entries(stats.byType)) {
output += ` ${type}: ${data.count} opps, ${data.taken} taken, ${data.winRate.toFixed(0)}% WR, $${data.profit.toFixed(2)}, avg edge ${data.avgEdge.toFixed(2)}%\n`;
}
return output;
}
case 'by-strategy': {
const periodFlag = parts.find((p: string) => p.match(/^\d+d$/));
const days = periodFlag ? parseInt(periodFlag, 10) : 30;
const strategies = analytics.getBestStrategies({ days, minSamples: 1 });
if (!strategies.length) return 'No strategy data yet.';
let output = `**Performance by Strategy** (${days}d)\n\n`;
for (const s of strategies) {
output += ` ${s.type}`;
if (s.platformPair) output += ` (${s.platformPair.join(' <-> ')})`;
output += `: ${s.samples} trades, ${s.winRate.toFixed(0)}% WR, avg $${s.avgProfit.toFixed(2)}\n`;
}
return output;
}
case 'best-times':
case 'by-hour':
case 'by-day': {
const periodFlag = parts.find((p: string) => p.match(/^\d+d$/));
const days = periodFlag ? parseInt(periodFlag, 10) : 30;
const attr = analytics.getPerformanceAttribution({ days });
let output = '';
if (cmd === 'by-day') {
const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
output = `**Performance by Day of Week** (${days}d)\n\n`;
for (let d = 0; d < 7; d++) {
const bucket = attr.byDayOfWeek[d];
if (!bucket || bucket.count === 0) continue;
output += ` ${dayNames[d]}: ${bucket.count} opps, ${bucket.winRate.toFixed(0)}% WR, $${bucket.totalPnL.toFixed(2)} PnL\n`;
}
} else {
output = `**Performance by Hour (UTC)** (${days}d)\n\n`;
const hourEntries: Array<{ hour: number; bucket: typeof attr.byHour[0] }> = [];
for (let h = 0; h < 24; h++) {
const bucket = attr.byHour[h];
if (bucket && bucket.count > 0) {
hourEntries.push({ hour: h, bucket });
}
}
if (cmd === 'best-times') {
hourEntries.sort((a, b) => b.bucket.avgPnL - a.bucket.avgPnL);
output = `**Best Trading Hours (UTC)** (${days}d)\n\n`;
}
for (const { hour, bucket } of hourEntries) {
output += ` ${String(hour).padStart(2, '0')}:00: ${bucket.count} opps, ${bucket.winRate.toFixed(0)}% WR, avg $${bucket.avgPnL.toFixed(2)}, total $${bucket.totalPnL.toFixed(2)}\n`;
}
}
if (output.endsWith('\n\n')) output += 'No time data yet.\n';
return output;
}
case 'edge-decay': {
const typeFlag = parts.indexOf('--type');
const type = typeFlag >= 0 ? parts[typeFlag + 1] : undefined;
const periodFlag = parts.find((p: string) => p.match(/^\d+d$/));
const days = periodFlag ? parseInt(periodFlag, 10) : 30;
const decay = analytics.getEdgeDecayAnalysis({ type, days });
if (decay.decayCurve.length === 0) return 'No edge decay data yet.';
let output = `**Edge Decay Analysis** (${days}d)\n\n`;
output += `Avg lifespan: ${(decay.avgLifespanMs / 60000).toFixed(1)} minutes\n\n`;
output += `**Decay Curve:**\n`;
for (const point of decay.decayCurve) {
const bar = '#'.repeat(Math.round(point.remainingEdgePct * 2));
output += ` ${String(point.minutesSinceDiscovery).padStart(3)}m: ${point.remainingEdgePct.toFixed(2)}% ${bar}\n`;
}
return output;
}
case 'edge-buckets': {
const periodFlag = parts.find((p: string) => p.match(/^\d+d$/));
const days = periodFlag ? parseInt(periodFlag, 10) : 30;
const attr = analytics.getPerformanceAttribution({ days });
let output = `**Performance by Edge Size** (${days}d)\n\n`;
const labels: Record<string, string> = {
tiny: '< 1%',
small: '1-2%',
medium: '2-5%',
large: '5-10%',
huge: '> 10%',
};
let hasData = false;
for (const [key, bucket] of Object.entries(attr.byEdgeBucket)) {
if (bucket.count === 0) continue;
hasData = true;
output += ` ${labels[key] || key}: ${bucket.count} opps, ${bucket.winRate.toFixed(0)}% WR, avg $${bucket.avgPnL.toFixed(2)}, total $${bucket.totalPnL.toFixed(2)}\n`;
}
if (!hasData) output += 'No edge bucket data yet.\n';
return output;
}
case 'liquidity': {
const periodFlag = parts.find((p: string) => p.match(/^\d+d$/));
const days = periodFlag ? parseInt(periodFlag, 10) : 30;
const attr = analytics.getPerformanceAttribution({ days });
let output = `**Performance by Liquidity** (${days}d)\n\n`;
const labels: Record<string, string> = {
low: '< $500',
medium: '$500 - $5,000',
high: '> $5,000',
};
let hasData = false;
for (const [key, bucket] of Object.entries(attr.byLiquidityBucket)) {
if (bucket.count === 0) continue;
hasData = true;
output += ` ${labels[key] || key}: ${bucket.count} opps, ${bucket.winRate.toFixed(0)}% WR, avg $${bucket.avgPnL.toFixed(2)}, total $${bucket.totalPnL.toFixed(2)}\n`;
}
if (!hasData) output += 'No liquidity data yet.\n';
return output;
}
case 'platforms': {
const pairs = analytics.getPlatformPairs();
if (!pairs.length) return 'No platform pair data yet.';
let output = '**Platform Pair Performance**\n\n';
for (const p of pairs) {
output += `${p.platforms.join(' <-> ')}: ${p.count} opps, ${p.winRate.toFixed(0)}% WR, $${p.totalProfit.toFixed(2)} profit\n`;
}
return output;
}
case 'opportunities':
case 'list': {
const typeFlag = parts.indexOf('--type');
const type = typeFlag >= 0 ? parts[typeFlag + 1] : undefined;
const opps = analytics.getOpportunities({ type, limit: 20 });
if (!opps.length) return 'No opportunities recorded yet.';
let output = `**Recent Opportunities** (${opps.length})\n\n`;
for (const o of opps) {
output += `[${o.status}] ${o.type} — edge ${o.edgePct.toFixed(2)}%, score ${o.score}\n`;
}
return output;
}
default:
return helpText();
}
} catch (error) {
return `Error: ${error instanceof Error ? error.message : String(error)}`;
}
}
function helpText(): string {
return `**Analytics Commands**
/analytics - Summary stats
/analytics stats [Nd] - Performance statistics
/analytics today - Today's performance
/analytics week - Weekly breakdown
/analytics month - Monthly breakdown
/analytics attribution [Nd] - P&L by edge source
/analytics by-platform - P&L by platform
/analytics by-category - P&L by market category
/analytics by-strategy [Nd] - P&L by strategy
/analytics best-times [Nd] - Best trading hours
/analytics by-hour [Nd] - Hourly performance
/analytics by-day [Nd] - Day of week analysis
/analytics edge-decay [Nd] - Edge decay over time
/analytics edge-buckets [Nd] - Performance by edge size
/analytics liquidity [Nd] - Performance by liquidity
/analytics platforms - Platform pair performance
/analytics opportunities [--type X] - Browse opportunities`;
}
export default {
name: 'analytics',
description: 'Opportunity analytics, win rates, and performance tracking',
commands: ['/analytics'],
handle: execute,
};
Related skills
FAQ
What can it attribute P&L against?
Edge source, platform, market category, strategy, and time of trade, plus trade-size buckets.
Can it export reports?
Yes, it can export PDF reports and raw CSV data via exportReport and exportData.