- Created /analytics/optimization page with comprehensive UI - Displays MAE/MFE analysis with percentiles - Shows current TP/SL performance with hit rate bars - Visualizes optimal recommendations vs current levels - Projects impact of optimization (win rate, profit factor, P&L improvement) - Provides reasoning for each recommended level - Added navigation link from main analytics page Dashboard features: - Overview stats: total trades, win rate, profit factor, money left on table - MAE analysis: avg, median, 25th/75th percentile, worst - MFE analysis: avg, median, 25th/75th percentile, best - Current config: TP1/TP2/SL hit rates with progress bars - Recommendations: optimal levels with color-coded cards - Reasoning cards: explanation for each recommendation - Projected impact: win rate change, profit factor change, profit improvement - Direct link to Settings page to apply recommendations Access at: http://localhost:3001/analytics/optimization Phase 1-4 Complete! System now tracks MAE/MFE, captures market context, analyzes performance, and provides data-driven TP/SL recommendations.
418 lines
15 KiB
TypeScript
418 lines
15 KiB
TypeScript
'use client'
|
||
|
||
import { useEffect, useState } from 'react'
|
||
import Link from 'next/link'
|
||
|
||
interface TPSLAnalysis {
|
||
success: boolean
|
||
analysis?: {
|
||
totalTrades: number
|
||
winningTrades: number
|
||
losingTrades: number
|
||
winRate: number
|
||
avgWin: number
|
||
avgLoss: number
|
||
profitFactor: number
|
||
|
||
maeAnalysis: {
|
||
avgMAE: number
|
||
medianMAE: number
|
||
percentile25MAE: number
|
||
percentile75MAE: number
|
||
worstMAE: number
|
||
}
|
||
|
||
mfeAnalysis: {
|
||
avgMFE: number
|
||
medianMFE: number
|
||
percentile25MFE: number
|
||
percentile75MFE: number
|
||
bestMFE: number
|
||
}
|
||
|
||
currentLevels: {
|
||
tp1Percent: number
|
||
tp2Percent: number
|
||
slPercent: number
|
||
tp1HitRate: number
|
||
tp2HitRate: number
|
||
slHitRate: number
|
||
moneyLeftOnTable: number
|
||
}
|
||
|
||
recommendations: {
|
||
optimalTP1: number
|
||
optimalTP2: number
|
||
optimalSL: number
|
||
|
||
reasoning: {
|
||
tp1: string
|
||
tp2: string
|
||
sl: string
|
||
}
|
||
|
||
projectedImpact: {
|
||
expectedWinRateChange: number
|
||
expectedProfitFactorChange: number
|
||
estimatedProfitImprovement: number
|
||
}
|
||
}
|
||
|
||
tradesByOutcome: {
|
||
tp1Exits: number
|
||
tp2Exits: number
|
||
slExits: number
|
||
manualExits: number
|
||
}
|
||
}
|
||
error?: string
|
||
}
|
||
|
||
export default function OptimizationPage() {
|
||
const [analysis, setAnalysis] = useState<TPSLAnalysis | null>(null)
|
||
const [loading, setLoading] = useState(true)
|
||
const [error, setError] = useState<string | null>(null)
|
||
|
||
useEffect(() => {
|
||
fetchAnalysis()
|
||
}, [])
|
||
|
||
const fetchAnalysis = async () => {
|
||
try {
|
||
setLoading(true)
|
||
setError(null)
|
||
|
||
const response = await fetch('/api/analytics/tp-sl-optimization')
|
||
const data = await response.json()
|
||
|
||
setAnalysis(data)
|
||
|
||
if (!data.success) {
|
||
setError(data.error || 'Failed to load analysis')
|
||
}
|
||
} catch (err) {
|
||
setError('Failed to fetch analytics: ' + (err as Error).message)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900">
|
||
<Header />
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||
<div className="flex items-center justify-center h-64">
|
||
<div className="text-center">
|
||
<div className="inline-block animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mb-4"></div>
|
||
<p className="text-gray-400">Loading optimization analysis...</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
if (error || !analysis?.success) {
|
||
return (
|
||
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900">
|
||
<Header />
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||
<div className="bg-yellow-900/20 border border-yellow-600 rounded-lg p-6">
|
||
<h2 className="text-xl font-semibold text-yellow-400 mb-2">⚠️ Insufficient Data</h2>
|
||
<p className="text-gray-300 mb-4">{error || analysis?.error}</p>
|
||
<p className="text-sm text-gray-400 mb-4">
|
||
Need at least 10 closed trades with MAE/MFE tracking data.
|
||
The next trades you take will automatically track this data.
|
||
</p>
|
||
<button
|
||
onClick={fetchAnalysis}
|
||
className="px-6 py-3 bg-blue-600 hover:bg-blue-700 rounded-lg font-semibold transition-colors"
|
||
>
|
||
🔄 Refresh Analytics
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
const data = analysis.analysis!
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900">
|
||
<Header />
|
||
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||
{/* Header with Refresh */}
|
||
<div className="flex items-center justify-between mb-8">
|
||
<div>
|
||
<h2 className="text-2xl font-bold text-white">💡 TP/SL Optimization</h2>
|
||
<p className="text-sm text-gray-400 mt-1">Based on {data.totalTrades} trades with MAE/MFE data</p>
|
||
</div>
|
||
<button
|
||
onClick={fetchAnalysis}
|
||
className="px-4 py-2 bg-gray-800 hover:bg-gray-700 rounded-lg transition-colors text-white"
|
||
>
|
||
🔄 Refresh
|
||
</button>
|
||
</div>
|
||
|
||
{/* Overview Stats */}
|
||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
|
||
<StatCard title="Total Trades" value={data.totalTrades.toString()} />
|
||
<StatCard
|
||
title="Win Rate"
|
||
value={data.winRate.toFixed(1) + '%'}
|
||
valueColor="text-green-400"
|
||
/>
|
||
<StatCard
|
||
title="Profit Factor"
|
||
value={data.profitFactor.toFixed(2)}
|
||
valueColor="text-blue-400"
|
||
/>
|
||
<StatCard
|
||
title="Money Left on Table"
|
||
value={'$' + data.currentLevels.moneyLeftOnTable.toFixed(2)}
|
||
valueColor="text-yellow-400"
|
||
/>
|
||
</div>
|
||
|
||
{/* MAE/MFE Analysis */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
|
||
<h3 className="text-xl font-semibold mb-4 text-green-400">
|
||
📈 Maximum Favorable Excursion (MFE)
|
||
</h3>
|
||
<div className="space-y-3">
|
||
<MetricRow label="Average" value={data.mfeAnalysis.avgMFE.toFixed(2) + '%'} />
|
||
<MetricRow label="Median" value={data.mfeAnalysis.medianMFE.toFixed(2) + '%'} />
|
||
<MetricRow label="25th Percentile" value={data.mfeAnalysis.percentile25MFE.toFixed(2) + '%'} />
|
||
<MetricRow label="75th Percentile" value={data.mfeAnalysis.percentile75MFE.toFixed(2) + '%'} />
|
||
<div className="border-t border-gray-700 pt-3">
|
||
<MetricRow
|
||
label="Best"
|
||
value={data.mfeAnalysis.bestMFE.toFixed(2) + '%'}
|
||
valueColor="text-green-400 font-bold"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
|
||
<h3 className="text-xl font-semibold mb-4 text-red-400">
|
||
📉 Maximum Adverse Excursion (MAE)
|
||
</h3>
|
||
<div className="space-y-3">
|
||
<MetricRow label="Average" value={data.maeAnalysis.avgMAE.toFixed(2) + '%'} />
|
||
<MetricRow label="Median" value={data.maeAnalysis.medianMAE.toFixed(2) + '%'} />
|
||
<MetricRow label="25th Percentile" value={data.maeAnalysis.percentile25MAE.toFixed(2) + '%'} />
|
||
<MetricRow label="75th Percentile" value={data.maeAnalysis.percentile75MAE.toFixed(2) + '%'} />
|
||
<div className="border-t border-gray-700 pt-3">
|
||
<MetricRow
|
||
label="Worst"
|
||
value={data.maeAnalysis.worstMAE.toFixed(2) + '%'}
|
||
valueColor="text-red-400 font-bold"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Current Configuration Performance */}
|
||
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700 mb-8">
|
||
<h3 className="text-xl font-semibold mb-6 text-white">🎯 Current Configuration Performance</h3>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||
<HitRateBar
|
||
label={'TP1: ' + data.currentLevels.tp1Percent + '%'}
|
||
hitRate={data.currentLevels.tp1HitRate}
|
||
exits={data.tradesByOutcome.tp1Exits}
|
||
color="bg-green-500"
|
||
/>
|
||
<HitRateBar
|
||
label={'TP2: ' + data.currentLevels.tp2Percent + '%'}
|
||
hitRate={data.currentLevels.tp2HitRate}
|
||
exits={data.tradesByOutcome.tp2Exits}
|
||
color="bg-blue-500"
|
||
/>
|
||
<HitRateBar
|
||
label={'SL: ' + data.currentLevels.slPercent + '%'}
|
||
hitRate={data.currentLevels.slHitRate}
|
||
exits={data.tradesByOutcome.slExits}
|
||
color="bg-red-500"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Recommendations */}
|
||
<div className="bg-gradient-to-r from-blue-900/30 to-purple-900/30 border border-blue-700 rounded-xl p-6 mb-8">
|
||
<h2 className="text-2xl font-bold mb-6 text-white">💡 Optimization Recommendations</h2>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
|
||
<RecommendationCard
|
||
label="Optimal TP1"
|
||
value={data.recommendations.optimalTP1.toFixed(2) + '%'}
|
||
current={data.currentLevels.tp1Percent + '%'}
|
||
color="text-green-400"
|
||
/>
|
||
<RecommendationCard
|
||
label="Optimal TP2"
|
||
value={data.recommendations.optimalTP2.toFixed(2) + '%'}
|
||
current={data.currentLevels.tp2Percent + '%'}
|
||
color="text-blue-400"
|
||
/>
|
||
<RecommendationCard
|
||
label="Optimal SL"
|
||
value={data.recommendations.optimalSL.toFixed(2) + '%'}
|
||
current={data.currentLevels.slPercent + '%'}
|
||
color="text-red-400"
|
||
/>
|
||
</div>
|
||
|
||
{/* Reasoning */}
|
||
<div className="space-y-3 mb-6">
|
||
<ReasoningCard
|
||
label="TP1 Reasoning"
|
||
text={data.recommendations.reasoning.tp1}
|
||
color="border-green-700 bg-green-900/20"
|
||
/>
|
||
<ReasoningCard
|
||
label="TP2 Reasoning"
|
||
text={data.recommendations.reasoning.tp2}
|
||
color="border-blue-700 bg-blue-900/20"
|
||
/>
|
||
<ReasoningCard
|
||
label="SL Reasoning"
|
||
text={data.recommendations.reasoning.sl}
|
||
color="border-red-700 bg-red-900/20"
|
||
/>
|
||
</div>
|
||
|
||
{/* Projected Impact */}
|
||
<div className="bg-gray-800/50 rounded-lg p-6 border border-gray-700">
|
||
<h3 className="text-lg font-semibold mb-4 text-white">📊 Projected Impact</h3>
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
<ImpactMetric
|
||
label="Win Rate Change"
|
||
value={data.recommendations.projectedImpact.expectedWinRateChange.toFixed(1) + '%'}
|
||
positive={data.recommendations.projectedImpact.expectedWinRateChange >= 0}
|
||
/>
|
||
<ImpactMetric
|
||
label="Profit Factor Change"
|
||
value={data.recommendations.projectedImpact.expectedProfitFactorChange.toFixed(2)}
|
||
positive={data.recommendations.projectedImpact.expectedProfitFactorChange >= 0}
|
||
/>
|
||
<ImpactMetric
|
||
label="Profit Improvement"
|
||
value={data.recommendations.projectedImpact.estimatedProfitImprovement.toFixed(1) + '%'}
|
||
positive={data.recommendations.projectedImpact.estimatedProfitImprovement >= 0}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Action Button */}
|
||
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700 text-center">
|
||
<p className="text-gray-400 mb-4">
|
||
Ready to apply these optimized levels? Update your configuration in Settings.
|
||
</p>
|
||
<Link
|
||
href="/settings"
|
||
className="inline-block px-8 py-3 bg-blue-600 hover:bg-blue-700 rounded-lg font-semibold transition-colors text-white"
|
||
>
|
||
⚙️ Go to Settings
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Component helpers
|
||
function Header() {
|
||
return (
|
||
<div className="bg-gray-800/50 backdrop-blur-sm border-b border-gray-700">
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||
<div className="flex items-center space-x-4">
|
||
<Link href="/analytics" className="text-gray-400 hover:text-white transition">
|
||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||
</svg>
|
||
</Link>
|
||
<div>
|
||
<h1 className="text-2xl font-bold text-white">🎯 TP/SL Optimization</h1>
|
||
<p className="text-sm text-gray-400">Data-driven recommendations for optimal exit levels</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function StatCard({ title, value, valueColor = 'text-white' }: { title: string, value: string, valueColor?: string }) {
|
||
return (
|
||
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
|
||
<div className="text-sm text-gray-400 mb-1">{title}</div>
|
||
<div className={'text-2xl font-bold ' + valueColor}>{value}</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function MetricRow({ label, value, valueColor = 'text-white' }: { label: string, value: string, valueColor?: string }) {
|
||
return (
|
||
<div className="flex justify-between">
|
||
<span className="text-gray-400">{label}:</span>
|
||
<span className={'font-semibold ' + valueColor}>{value}</span>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function HitRateBar({ label, hitRate, exits, color }: { label: string, hitRate: number, exits: number, color: string }) {
|
||
return (
|
||
<div>
|
||
<div className="text-sm text-gray-400 mb-2">{label}</div>
|
||
<div className="bg-gray-700 rounded-full h-4 overflow-hidden">
|
||
<div
|
||
className={color + ' h-full transition-all duration-500'}
|
||
style={{ width: hitRate + '%' }}
|
||
/>
|
||
</div>
|
||
<div className="flex justify-between mt-1">
|
||
<div className="text-xs text-gray-400">Hit Rate: {hitRate.toFixed(1)}%</div>
|
||
<div className="text-xs text-gray-500">{exits} exits</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function RecommendationCard({ label, value, current, color }: { label: string, value: string, current: string, color: string }) {
|
||
return (
|
||
<div className="bg-gray-800/50 rounded-lg p-4 border border-gray-700">
|
||
<div className="text-sm text-gray-400 mb-1">{label}</div>
|
||
<div className={'text-3xl font-bold ' + color}>{value}</div>
|
||
<div className="text-xs text-gray-400 mt-2">Current: {current}</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function ReasoningCard({ label, text, color }: { label: string, text: string, color: string }) {
|
||
return (
|
||
<div className={'rounded-lg p-4 border ' + color}>
|
||
<div className="font-semibold text-white mb-1">{label}</div>
|
||
<div className="text-sm text-gray-300">{text}</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function ImpactMetric({ label, value, positive }: { label: string, value: string, positive: boolean }) {
|
||
return (
|
||
<div>
|
||
<div className="text-sm text-gray-400 mb-1">{label}</div>
|
||
<div className={'text-2xl font-bold ' + (positive ? 'text-green-400' : 'text-red-400')}>
|
||
{positive ? '+' : ''}{value}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|