c6e8f6fe1e
Hide sim equity when LIVE is selected; OKX funds bar uses exchange balances; add cross/isolated for perp only (options stay cash). Co-authored-by: Cursor <cursoragent@cursor.com>
149 lines
4.6 KiB
TypeScript
149 lines
4.6 KiB
TypeScript
import { useCallback, useEffect, useState } from "react";
|
|
import { apiFetch } from "../api/client";
|
|
|
|
export type FundsSummary = {
|
|
ok: boolean;
|
|
exchange: string;
|
|
mode?: string;
|
|
trading_day: string;
|
|
total_trades: number;
|
|
win_rate: number;
|
|
profit_loss_ratio: number | null;
|
|
total_funds: number | null;
|
|
funding_usdt: number | null;
|
|
trading_usdt: number | null;
|
|
funding_usdc?: number | null;
|
|
trading_usdc?: number | null;
|
|
funding_label?: string;
|
|
trading_label?: string;
|
|
realtime_pnl: number | null;
|
|
usdc_usdt_rate?: number;
|
|
perp_inst_id?: string;
|
|
detail?: string;
|
|
};
|
|
|
|
function fmtU(n: number | null | undefined) {
|
|
if (n == null || Number.isNaN(n)) return "—";
|
|
return `${n.toFixed(2)}U`;
|
|
}
|
|
|
|
function fmtUsdc(n: number | null | undefined) {
|
|
if (n == null || Number.isNaN(n)) return "—";
|
|
return `${n.toFixed(2)} USDC`;
|
|
}
|
|
|
|
function pnlClass(n: number | null | undefined) {
|
|
if (n == null || Number.isNaN(n) || n === 0) return "";
|
|
return n > 0 ? "pos-pnl-profit" : "pos-pnl-loss";
|
|
}
|
|
|
|
function DualCcy({
|
|
usdt,
|
|
usdc,
|
|
}: {
|
|
usdt: number | null | undefined;
|
|
usdc: number | null | undefined;
|
|
}) {
|
|
return (
|
|
<div className="funds-dual">
|
|
<div className="funds-value mono">{fmtU(usdt)}</div>
|
|
<div className="funds-value funds-value--sub mono">{fmtUsdc(usdc)}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function FundsBar() {
|
|
const [s, setS] = useState<FundsSummary | null>(null);
|
|
const [err, setErr] = useState("");
|
|
|
|
const load = useCallback(() => {
|
|
apiFetch<FundsSummary>("/api/funds/summary")
|
|
.then((r) => {
|
|
setS(r);
|
|
setErr(r.ok ? "" : r.detail || "资金摘要不可用");
|
|
})
|
|
.catch((e) => {
|
|
setErr(e instanceof Error ? e.message : String(e));
|
|
});
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
load();
|
|
const t = window.setInterval(load, 5000);
|
|
const onRefresh = () => load();
|
|
window.addEventListener("funds-refresh", onRefresh);
|
|
return () => {
|
|
window.clearInterval(t);
|
|
window.removeEventListener("funds-refresh", onRefresh);
|
|
};
|
|
}, [load]);
|
|
|
|
if (!s?.ok) {
|
|
if (!err) return null;
|
|
return (
|
|
<div className="funds-bar-shell">
|
|
<div className="funds-bar-inner">
|
|
<div className="funds-bar funds-bar--err" aria-label="资金摘要异常">
|
|
<span className="meta">{err}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="funds-bar-shell">
|
|
<div className="funds-bar-inner">
|
|
<div className="funds-bar" aria-label="资金与统计">
|
|
<div className="funds-item funds-item--primary">
|
|
<div className="funds-label">交易所</div>
|
|
<div className="funds-value">{s.exchange || "—"}</div>
|
|
</div>
|
|
<div className="funds-item">
|
|
<div className="funds-label">交易日</div>
|
|
<div className="funds-value mono">{s.trading_day}</div>
|
|
</div>
|
|
<div className="funds-item funds-item--primary">
|
|
<div className="funds-label">总交易</div>
|
|
<div className="funds-value mono">{s.total_trades}</div>
|
|
</div>
|
|
<div className="funds-item">
|
|
<div className="funds-label">胜率</div>
|
|
<div className="funds-value mono">
|
|
{(s.win_rate * 100).toFixed(0)}%
|
|
</div>
|
|
</div>
|
|
<div className="funds-item" title="平均盈利 ÷ 平均亏损">
|
|
<div className="funds-label">盈亏比</div>
|
|
<div className="funds-value mono">
|
|
{s.profit_loss_ratio != null
|
|
? s.profit_loss_ratio.toFixed(2)
|
|
: "—"}
|
|
</div>
|
|
</div>
|
|
<div className="funds-item">
|
|
<div className="funds-label">总资金</div>
|
|
<div className="funds-value mono">{fmtU(s.total_funds)}</div>
|
|
</div>
|
|
<div className="funds-item funds-item--dual">
|
|
<div className="funds-label">资金账户</div>
|
|
<DualCcy usdt={s.funding_usdt} usdc={s.funding_usdc} />
|
|
</div>
|
|
<div className="funds-item funds-item--dual">
|
|
<div className="funds-label">交易账户</div>
|
|
<DualCcy usdt={s.trading_usdt} usdc={s.trading_usdc} />
|
|
</div>
|
|
<div className="funds-item funds-item--pnl">
|
|
<div className="funds-label">实时盈亏</div>
|
|
<div className={`funds-value mono ${pnlClass(s.realtime_pnl)}`}>
|
|
{s.realtime_pnl == null
|
|
? "—"
|
|
: `${s.realtime_pnl > 0 ? "+" : ""}${fmtU(s.realtime_pnl)}`}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|