feat: improve trades page with amount/profit/deadline
This commit is contained in:
parent
e049f57f50
commit
956469460c
@ -17,6 +17,11 @@ async function getStats() {
|
||||
catch { return []; }
|
||||
}
|
||||
|
||||
function calcExpectedProfit(direction: string, entryPrice: number, betAmount: number): number {
|
||||
const price = direction === "YES" ? entryPrice : 1 - entryPrice;
|
||||
return betAmount * (1 / price - 1);
|
||||
}
|
||||
|
||||
const statusMap: Record<string, string> = { open: "进行中", won: "盈利 ✅", lost: "亏损 ❌" };
|
||||
const statusVariant: Record<string, "default" | "secondary" | "destructive"> = {
|
||||
open: "secondary", won: "default", lost: "destructive"
|
||||
@ -26,6 +31,9 @@ export default async function TradesPage() {
|
||||
const [rows, stats] = await Promise.all([getTrades(), getStats()]);
|
||||
const statsMap = Object.fromEntries(stats.map((s: any[]) => [s[0], s[1]]));
|
||||
const totalPnl = rows.reduce((sum: number, r: any[]) => sum + (r[7] || 0), 0);
|
||||
const expectedTotalProfit = rows
|
||||
.filter((r: any[]) => r[6] === "open")
|
||||
.reduce((sum: number, r: any[]) => sum + calcExpectedProfit(r[2], Number(r[3]), Number(r[4])), 0);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@ -34,30 +42,38 @@ export default async function TradesPage() {
|
||||
<p className="text-muted-foreground text-sm mt-1">信号验证 — 模拟下注记录(每注 $1)</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-3">
|
||||
<Card>
|
||||
<CardContent className="pt-4">
|
||||
<div className="text-sm text-muted-foreground">总注单</div>
|
||||
<CardContent className="pt-4 pb-4">
|
||||
<div className="text-xs text-muted-foreground">总注单</div>
|
||||
<div className="text-2xl font-bold mt-1">{rows.length}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-4">
|
||||
<div className="text-sm text-muted-foreground">进行中</div>
|
||||
<div className="text-2xl font-bold mt-1">{statsMap['open'] || 0}</div>
|
||||
<CardContent className="pt-4 pb-4">
|
||||
<div className="text-xs text-muted-foreground">进行中</div>
|
||||
<div className="text-2xl font-bold mt-1">{statsMap["open"] || 0}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-4">
|
||||
<div className="text-sm text-muted-foreground">已盈利</div>
|
||||
<div className="text-2xl font-bold mt-1 text-green-400">{statsMap['won'] || 0}</div>
|
||||
<CardContent className="pt-4 pb-4">
|
||||
<div className="text-xs text-muted-foreground">已盈利</div>
|
||||
<div className="text-2xl font-bold mt-1 text-green-400">{statsMap["won"] || 0}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-4">
|
||||
<div className="text-sm text-muted-foreground">模拟总盈亏</div>
|
||||
<div className={`text-2xl font-bold mt-1 ${totalPnl >= 0 ? 'text-green-400' : 'text-red-400'}`}>
|
||||
{totalPnl >= 0 ? '+' : ''}{totalPnl.toFixed(2)}
|
||||
<CardContent className="pt-4 pb-4">
|
||||
<div className="text-xs text-muted-foreground">模拟总盈亏</div>
|
||||
<div className={`text-2xl font-bold mt-1 ${totalPnl >= 0 ? "text-green-400" : "text-red-400"}`}>
|
||||
{totalPnl >= 0 ? "+" : ""}{totalPnl.toFixed(2)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-4 pb-4">
|
||||
<div className="text-xs text-muted-foreground">预期总收益</div>
|
||||
<div className={`text-2xl font-bold mt-1 ${expectedTotalProfit >= 0 ? "text-blue-400" : "text-red-400"}`}>
|
||||
{expectedTotalProfit >= 0 ? "+$" : "-$"}{Math.abs(expectedTotalProfit).toFixed(2)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -66,43 +82,58 @@ export default async function TradesPage() {
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<table className="w-full text-xs">
|
||||
<thead>
|
||||
<tr className="border-b border-border">
|
||||
<th className="text-left px-4 py-3 text-muted-foreground font-medium text-xs uppercase tracking-wider">市场</th>
|
||||
<th className="text-left px-4 py-3 text-muted-foreground font-medium text-xs uppercase tracking-wider">方向</th>
|
||||
<th className="text-left px-4 py-3 text-muted-foreground font-medium text-xs uppercase tracking-wider">入场价</th>
|
||||
<th className="text-left px-4 py-3 text-muted-foreground font-medium text-xs uppercase tracking-wider hidden md:table-cell">到期日</th>
|
||||
<th className="text-left px-4 py-3 text-muted-foreground font-medium text-xs uppercase tracking-wider">状态</th>
|
||||
<th className="text-left px-4 py-3 text-muted-foreground font-medium text-xs uppercase tracking-wider">盈亏</th>
|
||||
<tr className="border-b border-border bg-muted/30">
|
||||
<th className="text-left px-3 py-2 text-muted-foreground font-medium uppercase tracking-wider">市场问题</th>
|
||||
<th className="text-left px-3 py-2 text-muted-foreground font-medium uppercase tracking-wider">方向</th>
|
||||
<th className="text-left px-3 py-2 text-muted-foreground font-medium uppercase tracking-wider">入场价</th>
|
||||
<th className="text-left px-3 py-2 text-muted-foreground font-medium uppercase tracking-wider hidden sm:table-cell">下注额</th>
|
||||
<th className="text-left px-3 py-2 text-muted-foreground font-medium uppercase tracking-wider hidden sm:table-cell">预期收益</th>
|
||||
<th className="text-left px-3 py-2 text-muted-foreground font-medium uppercase tracking-wider">到期日</th>
|
||||
<th className="text-left px-3 py-2 text-muted-foreground font-medium uppercase tracking-wider">状态</th>
|
||||
<th className="text-left px-3 py-2 text-muted-foreground font-medium uppercase tracking-wider">盈亏</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.length === 0 ? (
|
||||
<tr><td colSpan={6} className="text-center py-8 text-muted-foreground">暂无模拟交易</td></tr>
|
||||
) : rows.map((r: any[], i: number) => (
|
||||
<tr><td colSpan={8} className="text-center py-8 text-muted-foreground">暂无模拟交易</td></tr>
|
||||
) : rows.map((r: any[], i: number) => {
|
||||
const expProfit = calcExpectedProfit(r[2], Number(r[3]), Number(r[4]));
|
||||
return (
|
||||
<tr key={i} className="border-b border-border last:border-0 hover:bg-accent/50 transition-colors">
|
||||
<td className="px-4 py-3 max-w-xs">
|
||||
<div className="font-mono text-xs text-muted-foreground">{String(r[0]).slice(0, 8)}</div>
|
||||
<div className="text-xs mt-0.5 truncate max-w-[200px]">{r[1]}</div>
|
||||
<td className="px-3 py-2 max-w-[160px] sm:max-w-xs md:max-w-sm">
|
||||
<div className="truncate font-medium leading-tight" title={r[1]}>{r[1]}</div>
|
||||
<div className="font-mono text-[10px] text-muted-foreground mt-0.5">{String(r[0]).slice(0, 8)}</div>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<span className={`font-bold text-xs px-2 py-1 rounded ${r[2] === 'YES' ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'}`}>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
<span className={`font-bold px-1.5 py-0.5 rounded ${r[2] === "YES" ? "bg-green-500/20 text-green-400" : "bg-red-500/20 text-red-400"}`}>
|
||||
{r[2]}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 font-mono">{(Number(r[3]) * 100).toFixed(1)}¢</td>
|
||||
<td className="px-4 py-3 font-mono text-xs text-muted-foreground hidden md:table-cell">{String(r[5]).slice(0, 10)}</td>
|
||||
<td className="px-4 py-3"><Badge variant={statusVariant[r[6]] ?? "secondary"}>{statusMap[r[6]] ?? r[6]}</Badge></td>
|
||||
<td className="px-4 py-3">
|
||||
<td className="px-3 py-2 font-mono whitespace-nowrap">{(Number(r[3]) * 100).toFixed(1)}¢</td>
|
||||
<td className="px-3 py-2 font-mono whitespace-nowrap hidden sm:table-cell">${Number(r[4]).toFixed(2)}</td>
|
||||
<td className="px-3 py-2 font-mono whitespace-nowrap hidden sm:table-cell">
|
||||
{r[6] === "open" ? (
|
||||
<span className="text-blue-400">+${expProfit.toFixed(2)}</span>
|
||||
) : <span className="text-muted-foreground">-</span>}
|
||||
</td>
|
||||
<td className="px-3 py-2 font-mono whitespace-nowrap text-muted-foreground">{String(r[5]).slice(0, 10)}</td>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
<Badge variant={statusVariant[r[6]] ?? "secondary"} className="text-[10px] px-1.5 py-0">
|
||||
{statusMap[r[6]] ?? r[6]}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-3 py-2 font-mono whitespace-nowrap">
|
||||
{r[7] != null ? (
|
||||
<span className={r[7] >= 0 ? 'text-green-400' : 'text-red-400'}>
|
||||
{r[7] >= 0 ? '+' : ''}{Number(r[7]).toFixed(2)}
|
||||
<span className={r[7] >= 0 ? "text-green-400" : "text-red-400"}>
|
||||
{r[7] >= 0 ? "+" : ""}{Number(r[7]).toFixed(2)}
|
||||
</span>
|
||||
) : <span className="text-muted-foreground">-</span>}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user