Files
trading_bot_v4/app/api/analytics/version-comparison/route.ts
mindesbunister bba91c1df8 feat: Archive old indicator versions, focus on v8 production system
Version Management:
- v8 Money Line: PRODUCTION (8 trades, 57.1% WR, +$262.70, quality ≥95 = 100% wins)
- v5/v6/v7: ARCHIVED (historical baseline for future v9 comparison)

API Changes (app/api/analytics/version-comparison/route.ts):
- Added 'archived' flag to version stats
- Added 'production' field pointing to v8
- Updated sort order: v8 first, then archived versions
- Enhanced descriptions with PRODUCTION/ARCHIVED labels

UI Changes (app/analytics/page.tsx):
- v8 highlighted with blue gradient + 🚀 PRODUCTION badge
- Archived versions greyed out (60% opacity) + ARCHIVED badge
- Updated header: 'Indicator Versions (v8 Production)'
- Data collection notice: v8 shows 8/50 trades progress
- Kept comparison infrastructure for future v9 development

Documentation (.github/copilot-instructions.md):
- Updated Indicator Version Tracking section
- Documented perfect quality separation (≥95 = 100% wins)
- Clarified v8 production status, archived versions purpose
- Analytics UI behavior documented

Purpose: Keep comparison infrastructure for statistical validation
and future v9 testing while focusing user attention on v8 results.
2025-11-22 14:45:48 +01:00

165 lines
5.3 KiB
TypeScript

/**
* Trading Bot v4 - Indicator Version Comparison API
*
* Primary: v8 Money Line (Nov 18+) - Production system
* Archived: v5/v6/unknown - Historical baseline for comparison
*
* Returns performance metrics for statistical validation and future v9 testing
*/
import { NextResponse } from 'next/server'
import { getPrismaClient } from '@/lib/database/trades'
export const dynamic = 'force-dynamic'
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
}
}
export async function GET() {
try {
const prisma = getPrismaClient()
// Get overall stats by version
const versionStats = await prisma.$queryRaw<Array<{
version: string | null
trades: bigint
wins: bigint
total_pnl: any
avg_pnl: any
avg_quality_score: any
avg_mfe: any
avg_mae: any
}>>`
SELECT
COALESCE("indicatorVersion", 'unknown') as version,
COUNT(*) as trades,
SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) as wins,
SUM("realizedPnL") as total_pnl,
AVG("realizedPnL") as avg_pnl,
AVG("realizedPnL") as avg_pnl,
ROUND(AVG("signalQualityScore")::numeric, 1) as avg_quality_score,
ROUND(AVG("maxFavorableExcursion")::numeric, 2) as avg_mfe,
ROUND(AVG("maxAdverseExcursion")::numeric, 2) as avg_mae
FROM "Trade"
WHERE "exitReason" IS NOT NULL
AND "exitReason" NOT LIKE '%CLEANUP%'
AND "isTestTrade" = false
AND ("signalSource" IS NULL OR "signalSource" != 'manual')
GROUP BY "indicatorVersion"
ORDER BY version DESC
`
// Get extreme position stats by version (< 15% price position)
const extremePositionStats = await prisma.$queryRaw<Array<{
version: string | null
trades: bigint
wins: bigint
total_pnl: any
avg_quality_score: any
}>>`
SELECT
COALESCE("indicatorVersion", 'unknown') as version,
COUNT(*) as trades,
SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) as wins,
SUM("realizedPnL") as total_pnl,
ROUND(AVG("signalQualityScore")::numeric, 1) as avg_quality_score
FROM "Trade"
WHERE "exitReason" IS NOT NULL
AND "exitReason" NOT LIKE '%CLEANUP%'
AND "isTestTrade" = false
AND ("signalSource" IS NULL OR "signalSource" != 'manual')
AND "pricePositionAtEntry" < 15
GROUP BY "indicatorVersion"
ORDER BY version DESC
`
// Build combined results
const results: VersionStats[] = versionStats.map(stat => {
const extremeStats = extremePositionStats.find(e =>
(e.version || 'unknown') === (stat.version || 'unknown')
)
const trades = Number(stat.trades)
const wins = Number(stat.wins)
const extremeCount = extremeStats ? Number(extremeStats.trades) : 0
const extremeWins = extremeStats ? Number(extremeStats.wins) : 0
return {
version: stat.version || 'v1',
tradeCount: trades,
winRate: trades > 0 ? Math.round((wins / trades) * 100 * 10) / 10 : 0,
totalPnL: Number(stat.total_pnl) || 0,
avgPnL: Number(stat.avg_pnl) || 0,
avgQualityScore: stat.avg_quality_score ? Number(stat.avg_quality_score) : null,
avgMFE: stat.avg_mfe ? Number(stat.avg_mfe) : null,
avgMAE: stat.avg_mae ? Number(stat.avg_mae) : null,
extremePositions: {
count: extremeCount,
avgADX: null,
weakADXCount: 0,
winRate: extremeCount > 0 ? Math.round((extremeWins / extremeCount) * 100 * 10) / 10 : 0,
avgPnL: extremeStats?.total_pnl ? Number(extremeStats.total_pnl) / extremeCount : 0,
}
}
})
// Sort versions: v8 first (production), then v7, v6, v5, unknown (archived)
const versionOrder: Record<string, number> = {
'v8': 0, 'v7': 1, 'v6': 2, 'v5': 3, 'unknown': 4
}
results.sort((a, b) => {
const orderA = versionOrder[a.version] ?? 999
const orderB = versionOrder[b.version] ?? 999
return orderA - orderB
})
// Mark archived versions
const resultsWithArchived = results.map(r => ({
...r,
archived: archivedVersions.includes(r.version)
}))
// Get version descriptions and archived status
const versionDescriptions: Record<string, string> = {
'v8': 'Money Line Sticky Trend (Nov 18+) - PRODUCTION',
'v7': 'HalfTrend with toggles (deprecated)',
'v6': 'HalfTrend + BarColor (Nov 12-18) - ARCHIVED',
'v5': 'Buy/Sell Signal (pre-Nov 12) - ARCHIVED',
'unknown': 'No version tracked (pre-Nov 12) - ARCHIVED'
}
const archivedVersions = ['v5', 'v6', 'v7', 'unknown']
return NextResponse.json({
success: true,
versions: resultsWithArchived,
descriptions: versionDescriptions,
production: 'v8',
archived: archivedVersions,
timestamp: new Date().toISOString()
})
} catch (error) {
console.error('❌ Failed to fetch version comparison:', error)
return NextResponse.json(
{ success: false, error: 'Failed to fetch version comparison data' },
{ status: 500 }
)
}
}