Add signal quality version comparison to analytics dashboard

- Created /api/analytics/version-comparison endpoint
- Shows performance metrics for v1, v2, v3 scoring logic
- Compares: trade count, win rate, P&L, quality scores, MFE/MAE
- Special focus on extreme positions (< 15% or > 85% range)
- Tracks weak ADX count (< 18) for each version
- Visual indicators for current version (v3)
- Data collection progress notice for v3 (need 20+ trades)
- Legend explaining MFE, MAE, extreme positions, weak ADX

Enables data-driven optimization by comparing algorithm performance
with clean, version-tagged datasets.
This commit is contained in:
mindesbunister
2025-11-07 13:05:48 +01:00
parent 625dc44c59
commit 711ff9aaf4
3 changed files with 465 additions and 1 deletions

View File

@@ -67,10 +67,34 @@ interface PositionSummary {
netPositions: NetPosition[]
}
interface VersionStats {
version: string
tradeCount: number
winRate: number
totalPnL: number
avgPnL: number
avgQualityScore: number | null
avgMFE: number | null
avgMAE: number | null
extremePositions: {
count: number
avgADX: number | null
weakADXCount: number
winRate: number
avgPnL: number
}
}
interface VersionComparison {
versions: VersionStats[]
descriptions: Record<string, string>
}
export default function AnalyticsPage() {
const [stats, setStats] = useState<Stats | null>(null)
const [positions, setPositions] = useState<PositionSummary | null>(null)
const [lastTrade, setLastTrade] = useState<LastTrade | null>(null)
const [versionComparison, setVersionComparison] = useState<VersionComparison | null>(null)
const [loading, setLoading] = useState(true)
const [selectedDays, setSelectedDays] = useState(30)
@@ -81,19 +105,22 @@ export default function AnalyticsPage() {
const loadData = async () => {
setLoading(true)
try {
const [statsRes, positionsRes, lastTradeRes] = await Promise.all([
const [statsRes, positionsRes, lastTradeRes, versionRes] = await Promise.all([
fetch(`/api/analytics/stats?days=${selectedDays}`),
fetch('/api/analytics/positions'),
fetch('/api/analytics/last-trade'),
fetch('/api/analytics/version-comparison'),
])
const statsData = await statsRes.json()
const positionsData = await positionsRes.json()
const lastTradeData = await lastTradeRes.json()
const versionData = await versionRes.json()
setStats(statsData.stats)
setPositions(positionsData.summary)
setLastTrade(lastTradeData.trade)
setVersionComparison(versionData.success ? versionData : null)
} catch (error) {
console.error('Failed to load analytics:', error)
}
@@ -250,6 +277,180 @@ export default function AnalyticsPage() {
</div>
)}
{/* Signal Quality Version Comparison */}
{versionComparison && versionComparison.versions.length > 0 && (
<div className="mb-8">
<h2 className="text-xl font-bold text-white mb-4">🔬 Signal Quality Logic Versions</h2>
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
<p className="text-gray-300 text-sm mb-6 leading-relaxed">
The bot has evolved through different signal quality scoring algorithms.
This section compares their performance to enable data-driven optimization.
</p>
<div className="space-y-4">
{versionComparison.versions.map((version, idx) => {
const isCurrentVersion = version.version === 'v3'
return (
<div
key={version.version}
className={`p-5 rounded-lg border ${isCurrentVersion ? 'bg-blue-900/20 border-blue-500/50' : 'bg-gray-700/30 border-gray-600'}`}
>
<div className="flex items-start justify-between mb-4">
<div className="flex-1">
<div className="flex items-center space-x-3 mb-2">
<h3 className={`text-lg font-bold ${isCurrentVersion ? 'text-blue-400' : 'text-white'}`}>
{version.version.toUpperCase()}
{isCurrentVersion && (
<span className="ml-2 px-2 py-1 text-xs bg-blue-600 text-white rounded-full">
CURRENT
</span>
)}
</h3>
</div>
<p className="text-sm text-gray-400">
{versionComparison.descriptions[version.version] || 'Unknown version'}
</p>
</div>
</div>
{/* Main Metrics Grid */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
<div className="bg-gray-800/50 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Trades</div>
<div className="text-xl font-bold text-white">{version.tradeCount}</div>
</div>
<div className="bg-gray-800/50 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Win Rate</div>
<div className={`text-xl font-bold ${version.winRate >= 50 ? 'text-green-400' : 'text-red-400'}`}>
{version.winRate}%
</div>
</div>
<div className="bg-gray-800/50 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Total P&L</div>
<div className={`text-xl font-bold ${version.totalPnL >= 0 ? 'text-green-400' : 'text-red-400'}`}>
{version.totalPnL >= 0 ? '+' : ''}${version.totalPnL.toFixed(2)}
</div>
</div>
<div className="bg-gray-800/50 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Avg P&L</div>
<div className={`text-xl font-bold ${version.avgPnL >= 0 ? 'text-green-400' : 'text-red-400'}`}>
{version.avgPnL >= 0 ? '+' : ''}${version.avgPnL.toFixed(2)}
</div>
</div>
</div>
{/* Advanced Metrics */}
<div className="grid grid-cols-2 md:grid-cols-3 gap-3 mb-4">
{version.avgQualityScore !== null && (
<div className="bg-gray-800/50 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Avg Quality Score</div>
<div className={`text-lg font-semibold ${version.avgQualityScore >= 75 ? 'text-green-400' : 'text-yellow-400'}`}>
{version.avgQualityScore}/100
</div>
</div>
)}
{version.avgMFE !== null && (
<div className="bg-gray-800/50 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Avg MFE</div>
<div className="text-lg font-semibold text-green-400">
+{version.avgMFE.toFixed(2)}%
</div>
</div>
)}
{version.avgMAE !== null && (
<div className="bg-gray-800/50 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Avg MAE</div>
<div className="text-lg font-semibold text-red-400">
{version.avgMAE.toFixed(2)}%
</div>
</div>
)}
</div>
{/* Extreme Position Stats */}
{version.extremePositions.count > 0 && (
<div className="pt-4 border-t border-gray-600/50">
<div className="text-xs text-gray-400 mb-3 flex items-center">
<span className="text-yellow-500 mr-2"></span>
Extreme Positions (&lt; 15% or &gt; 85% range)
</div>
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
<div className="bg-gray-800/50 rounded p-2">
<div className="text-xs text-gray-500">Count</div>
<div className="text-sm font-semibold text-white">
{version.extremePositions.count}
</div>
</div>
{version.extremePositions.avgADX !== null && (
<div className="bg-gray-800/50 rounded p-2">
<div className="text-xs text-gray-500">Avg ADX</div>
<div className={`text-sm font-semibold ${version.extremePositions.avgADX >= 18 ? 'text-green-400' : 'text-orange-400'}`}>
{version.extremePositions.avgADX.toFixed(1)}
</div>
</div>
)}
<div className="bg-gray-800/50 rounded p-2">
<div className="text-xs text-gray-500">Weak ADX</div>
<div className="text-sm font-semibold text-orange-400">
{version.extremePositions.weakADXCount}
</div>
</div>
<div className="bg-gray-800/50 rounded p-2">
<div className="text-xs text-gray-500">Win Rate</div>
<div className={`text-sm font-semibold ${version.extremePositions.winRate >= 50 ? 'text-green-400' : 'text-red-400'}`}>
{version.extremePositions.winRate}%
</div>
</div>
<div className="bg-gray-800/50 rounded p-2">
<div className="text-xs text-gray-500">Avg P&L</div>
<div className={`text-sm font-semibold ${version.extremePositions.avgPnL >= 0 ? 'text-green-400' : 'text-red-400'}`}>
{version.extremePositions.avgPnL >= 0 ? '+' : ''}${version.extremePositions.avgPnL.toFixed(2)}
</div>
</div>
</div>
</div>
)}
{/* Data Collection Notice for v3 */}
{isCurrentVersion && version.tradeCount < 20 && (
<div className="mt-4 p-3 bg-yellow-900/20 rounded-lg border border-yellow-500/30">
<div className="flex items-start space-x-2">
<span className="text-yellow-500 text-sm">📊</span>
<p className="text-xs text-yellow-300/80 leading-relaxed">
<strong>Data Collection Phase:</strong> Need {20 - version.tradeCount} more trades
before v3 performance can be reliably evaluated. This version is designed to prevent
losses from extreme position entries with weak trends (ADX &lt; 18).
</p>
</div>
</div>
)}
</div>
)
})}
</div>
{/* Legend */}
<div className="mt-6 pt-6 border-t border-gray-600/50">
<div className="text-xs text-gray-400 space-y-1">
<div><strong className="text-gray-300">MFE (Max Favorable Excursion):</strong> Best profit % reached during trade lifetime</div>
<div><strong className="text-gray-300">MAE (Max Adverse Excursion):</strong> Worst loss % reached during trade lifetime</div>
<div><strong className="text-gray-300">Extreme Positions:</strong> Trades entered at price range extremes (&lt; 15% or &gt; 85%)</div>
<div><strong className="text-gray-300">Weak ADX:</strong> Trend strength below 18 (indicates sideways/choppy market)</div>
</div>
</div>
</div>
</div>
)}
{/* Last Trade Details */}
{lastTrade && (
<div className="mb-8">