Files
trading_bot_v4/app/api/analytics/version-comparison/route.ts
mindesbunister 08ee899164 feat: update analytics version descriptions
- Added v4 description: 'Frequency penalties + blocked signals tracking (Nov 11-14)'
- Added v5 description: 'Buy/Sell Signal strategy (pre-Nov 12)'
- Added v6 description: 'HalfTrend + BarColor strategy (Nov 12+)'

Context:
- v1-v4 = signalQualityVersion (scoring logic evolution)
- v5-v6 = indicatorVersion (TradingView strategy versions)
- Dashboard will now correctly label both types of versions
2025-11-14 13:07:01 +01:00

144 lines
4.7 KiB
TypeScript

/**
* Trading Bot v4 - Signal Quality Version Comparison API
*
* Returns performance metrics comparing different signal quality scoring versions
*/
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("signalQualityVersion", 'v1') 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
GROUP BY "signalQualityVersion"
ORDER BY version DESC
`
// Get extreme position stats by version (< 15% or > 85%)
const extremePositionStats = await prisma.$queryRaw<Array<{
version: string | null
count: bigint
avg_adx: any
weak_adx_count: bigint
wins: bigint
avg_pnl: any
}>>`
SELECT
COALESCE("signalQualityVersion", 'v1') as version,
COUNT(*) as count,
ROUND(AVG("adxAtEntry")::numeric, 1) as avg_adx,
COUNT(*) FILTER (WHERE "adxAtEntry" < 18) as weak_adx_count,
SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) as wins,
AVG("realizedPnL") as avg_pnl
FROM "Trade"
WHERE "exitReason" IS NOT NULL
AND "exitReason" NOT LIKE '%CLEANUP%'
AND "isTestTrade" = false
AND "pricePositionAtEntry" IS NOT NULL
AND ("pricePositionAtEntry" < 15 OR "pricePositionAtEntry" > 85)
GROUP BY "signalQualityVersion"
ORDER BY version DESC
`
// Build combined results
const results: VersionStats[] = versionStats.map(stat => {
const extremeStats = extremePositionStats.find(e =>
(e.version || 'v1') === (stat.version || 'v1')
)
const trades = Number(stat.trades)
const wins = Number(stat.wins)
const extremeCount = extremeStats ? Number(extremeStats.count) : 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: extremeStats?.avg_adx ? Number(extremeStats.avg_adx) : null,
weakADXCount: extremeStats ? Number(extremeStats.weak_adx_count) : 0,
winRate: extremeCount > 0 ? Math.round((extremeWins / extremeCount) * 100 * 10) / 10 : 0,
avgPnL: extremeStats?.avg_pnl ? Number(extremeStats.avg_pnl) : 0,
}
}
})
// Get version descriptions
const versionDescriptions: Record<string, string> = {
'v1': 'Original logic (price < 5% threshold)',
'v2': 'Added volume compensation for low ADX',
'v3': 'Stricter: ADX > 18 required for positions < 15%',
'v4': 'Frequency penalties + blocked signals tracking (Nov 11-14)',
'v5': 'Buy/Sell Signal strategy (pre-Nov 12)',
'v6': 'HalfTrend + BarColor strategy (Nov 12+)',
}
return NextResponse.json({
success: true,
versions: results,
descriptions: versionDescriptions,
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 }
)
}
}