139 lines
6.9 KiB
TypeScript
139 lines
6.9 KiB
TypeScript
'use client';
|
||
|
||
import React, { useEffect, useState } from 'react';
|
||
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
||
import { getProofOfYieldLedger, type ProofOfYieldLedgerResponse } from '@/lib/analytics-api';
|
||
|
||
export default function ProofOfYieldPage() {
|
||
const [data, setData] = useState<ProofOfYieldLedgerResponse | null>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
useEffect(() => {
|
||
async function loadData() {
|
||
try {
|
||
const ledger = await getProofOfYieldLedger(200);
|
||
setData(ledger);
|
||
} catch (error) {
|
||
console.error('Failed to load proof of yield data', error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
loadData();
|
||
}, []);
|
||
|
||
if (loading) {
|
||
return <div className="p-8 text-[#8a6b58] dot-matrix">載入歷史帳本資料中...</div>;
|
||
}
|
||
|
||
// 避免無資料時崩潰
|
||
const summary = data?.summary || {
|
||
roi_percent: 0,
|
||
win_rate_percent: 0,
|
||
avg_clv_percent: 0
|
||
};
|
||
|
||
const records = data?.records || [];
|
||
|
||
// 如果後端目前無真實獲利資料,產生一個平穩的空曲線
|
||
const equityData = records.length > 0 ? records.map((r, i) => ({
|
||
date: r.settled_at.split('T')[0],
|
||
equity: 10000 + (r.pnl || 0) * (i + 1) // 簡化處理
|
||
})) : [
|
||
{ date: new Date().toISOString().split('T')[0], equity: 10000 }
|
||
];
|
||
|
||
return (
|
||
<div className="min-h-screen bg-stone-50 p-8">
|
||
<div className="max-w-6xl mx-auto">
|
||
|
||
{/* 標題與核心指標 */}
|
||
<div className="mb-10">
|
||
<h1 className="text-3xl font-bold text-stone-900 uppercase tracking-widest border-b-2 border-quant-orange pb-2 inline-block">
|
||
公開收益驗證 <span className="text-stone-400 text-lg ml-2">公開獲利驗證</span>
|
||
</h1>
|
||
<p className="text-stone-500 mt-2">只呈現已寫入帳本且已結算的推薦,不用空曲線或示意績效包裝尚未發生的獲利。</p>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10">
|
||
<div className="bg-white p-6 rounded-lg border border-stone-200 shadow-sm">
|
||
<p className="text-sm text-stone-500 font-semibold">累積報酬率</p>
|
||
<p className={`text-4xl font-dotmatrix mt-2 ${summary.roi_percent >= 0 ? 'text-quant-orange' : 'text-red-500'}`}>
|
||
{summary.roi_percent > 0 ? '+' : ''}{summary.roi_percent.toFixed(2)}%
|
||
</p>
|
||
</div>
|
||
<div className="bg-white p-6 rounded-lg border border-stone-200 shadow-sm">
|
||
<p className="text-sm text-stone-500 uppercase font-semibold">Win Rate (勝率)</p>
|
||
<p className="text-4xl text-stone-800 font-dotmatrix mt-2">{summary.win_rate_percent.toFixed(2)}%</p>
|
||
</div>
|
||
<div className="bg-white p-6 rounded-lg border border-stone-200 shadow-sm relative overflow-hidden">
|
||
<div className="absolute right-0 top-0 w-2 h-full bg-quant-red"></div>
|
||
<p className="text-sm text-stone-500 font-semibold">平均收盤價差</p>
|
||
<p className={`text-4xl font-dotmatrix mt-2 ${summary.avg_clv_percent >= 0 ? 'text-quant-red' : 'text-red-500'}`}>
|
||
{summary.avg_clv_percent > 0 ? '+' : ''}{summary.avg_clv_percent.toFixed(2)}%
|
||
</p>
|
||
<p className="text-xs text-stone-400 mt-1">長期戰勝莊家的絕對指標</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 資金成長曲線 */}
|
||
<div className="bg-white p-6 rounded-lg border border-stone-200 shadow-sm mb-10 h-96">
|
||
<h3 className="text-lg font-bold text-stone-800 mb-6">資產成長曲線</h3>
|
||
{records.length === 0 ? (
|
||
<div className="flex h-full items-center justify-center text-sm text-stone-500">
|
||
尚無真實結算資料,因此不顯示模擬收益曲線。
|
||
</div>
|
||
) : (
|
||
<ResponsiveContainer width="100%" height="100%">
|
||
<AreaChart data={equityData} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
|
||
<defs>
|
||
<linearGradient id="colorEquity" x1="0" y1="0" x2="0" y2="1">
|
||
<stop offset="5%" stopColor="#ea580c" stopOpacity={0.4}/>
|
||
<stop offset="95%" stopColor="#ea580c" stopOpacity={0}/>
|
||
</linearGradient>
|
||
</defs>
|
||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f5f5f4" />
|
||
<XAxis dataKey="date" axisLine={false} tickLine={false} tick={{ fontSize: 12, fill: '#a8a29e' }} />
|
||
<YAxis domain={['auto', 'auto']} axisLine={false} tickLine={false} tick={{ fontSize: 12, fill: '#a8a29e' }} />
|
||
<Tooltip contentStyle={{ backgroundColor: '#1A1A1A', borderColor: '#ea580c', color: '#FAF6F0', fontFamily: '"DotGothic16", monospace' }} />
|
||
<Area type="monotone" dataKey="equity" stroke="#ea580c" strokeWidth={3} fillOpacity={1} fill="url(#colorEquity)" />
|
||
</AreaChart>
|
||
</ResponsiveContainer>
|
||
)}
|
||
</div>
|
||
|
||
{/* 歷史注單明細表 */}
|
||
<div className="bg-white rounded-lg border border-stone-200 shadow-sm overflow-hidden">
|
||
<table className="w-full text-left border-collapse">
|
||
<thead>
|
||
<tr className="bg-stone-100 text-stone-600 text-sm uppercase tracking-wider">
|
||
<th className="p-4 font-semibold">日期</th>
|
||
<th className="p-4 font-semibold">賽事 / 選項</th>
|
||
<th className="p-4 font-semibold">收盤價差</th>
|
||
<th className="p-4 font-semibold">結果</th>
|
||
<th className="p-4 font-semibold">損益</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-stone-200 font-dotmatrix text-lg">
|
||
{records.length === 0 ? (
|
||
<tr>
|
||
<td colSpan={5} className="p-4 text-center text-stone-500">尚無真實結算資料。</td>
|
||
</tr>
|
||
) : records.map((bet) => (
|
||
<tr key={bet.recommendation_id} className="hover:bg-stone-50 transition-colors">
|
||
<td className="p-4 text-stone-500">{bet.settled_at.split('T')[0]}</td>
|
||
<td className="p-4 text-stone-800 font-sans font-medium">{bet.match_id} <span className="text-stone-400 mx-2">|</span> <span className="text-quant-orange">{bet.selection}</span></td>
|
||
<td className="p-4 text-quant-red">{bet.clv_percent !== null ? `${bet.clv_percent.toFixed(2)}%` : '-'}</td>
|
||
<td className={`p-4 font-bold ${bet.is_win ? 'text-green-600' : 'text-stone-400'}`}>{bet.is_win ? '命中' : '未中'}</td>
|
||
<td className={`p-4 ${bet.pnl > 0 ? 'text-green-600' : 'text-red-500'}`}>{bet.pnl > 0 ? '+' : ''}{bet.pnl.toFixed(2)}</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|