Add OKX-style funds bar with USDT/USDC convert and transfer.
SIM uses multi-wallet balances; LIVE hits OKX asset/account APIs and spot USDC-USDT swap. Funds strip hides sim labeling. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -168,8 +168,7 @@ export default function PlanPage() {
|
||||
<div className="plan-desktop-head">
|
||||
<h2 style={{ marginTop: 0 }}>自动对冲计划</h2>
|
||||
<p style={{ color: "var(--muted)", marginTop: -8 }}>
|
||||
{plan?.mode === "LIVE" ? "LIVE 实盘下单" : "SIM 本地撮合"} · 行情{" "}
|
||||
{(snap?.exchange || "okx").toUpperCase()}
|
||||
行情 {(snap?.exchange || "okx").toUpperCase()}
|
||||
{snap?.perp_inst_id ? ` · ${snap.perp_inst_id}` : ""} ·
|
||||
目标平仓(双腿/远虚只平永续) · 未达标则到期结算
|
||||
{plan?.mode === "LIVE" && plan.live_ready === false
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
uploadRestoreBackup,
|
||||
} from "../api/client";
|
||||
|
||||
type Tab = "strategy" | "runtime" | "account" | "backup";
|
||||
type Tab = "strategy" | "runtime" | "funds" | "account" | "backup";
|
||||
type StratSub =
|
||||
| "position"
|
||||
| "select"
|
||||
@@ -104,6 +104,18 @@ export default function SettingsPage() {
|
||||
const [restoreFile, setRestoreFile] = useState<File | null>(null);
|
||||
const [restorePhrase, setRestorePhrase] = useState("");
|
||||
|
||||
const [fundsOk, setFundsOk] = useState("");
|
||||
const [fundsErr, setFundsErr] = useState("");
|
||||
const [convertDir, setConvertDir] = useState<"usdt_to_usdc" | "usdc_to_usdt">(
|
||||
"usdt_to_usdc",
|
||||
);
|
||||
const [convertAmt, setConvertAmt] = useState(100);
|
||||
const [xferCcy, setXferCcy] = useState<"USDT" | "USDC">("USDC");
|
||||
const [xferAmt, setXferAmt] = useState(50);
|
||||
const [xferFrom, setXferFrom] = useState("funding");
|
||||
const [xferTo, setXferTo] = useState("trading");
|
||||
const [usdcRate, setUsdcRate] = useState<number | null>(null);
|
||||
|
||||
function loadRuntime() {
|
||||
apiFetch<RuntimeSettings>("/api/settings/runtime")
|
||||
.then((r) => {
|
||||
@@ -426,6 +438,20 @@ export default function SettingsPage() {
|
||||
>
|
||||
运行模式
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={tab === "funds" ? "tab active" : "tab"}
|
||||
onClick={() => {
|
||||
setTab("funds");
|
||||
setFundsErr("");
|
||||
setFundsOk("");
|
||||
apiFetch<{ usdc_usdt_rate?: number }>("/api/funds/summary")
|
||||
.then((r) => setUsdcRate(r.usdc_usdt_rate ?? null))
|
||||
.catch(() => undefined);
|
||||
}}
|
||||
>
|
||||
资金
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={tab === "account" ? "tab active" : "tab"}
|
||||
@@ -1031,6 +1057,169 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{tab === "funds" ? (
|
||||
<div className="card settings-card">
|
||||
{fundsErr ? <div className="err">{fundsErr}</div> : null}
|
||||
{fundsOk ? <div className="settings-ok">{fundsOk}</div> : null}
|
||||
<section className="settings-section">
|
||||
<h3>币种兑换(USDT ↔ USDC)</h3>
|
||||
<p className="meta" style={{ marginTop: 0 }}>
|
||||
对齐 OKX 现货市价 USDC-USDT。参考价{" "}
|
||||
{usdcRate != null ? `1 USDC ≈ ${usdcRate.toFixed(4)} USDT` : "—"}
|
||||
。兑换发生在资金账户侧(USDT↔期权资金 USDC)。
|
||||
</p>
|
||||
<div className="settings-fields">
|
||||
<div className="field">
|
||||
<label htmlFor="convertDir">方向</label>
|
||||
<select
|
||||
id="convertDir"
|
||||
value={convertDir}
|
||||
onChange={(e) =>
|
||||
setConvertDir(
|
||||
e.target.value === "usdc_to_usdt"
|
||||
? "usdc_to_usdt"
|
||||
: "usdt_to_usdc",
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="usdt_to_usdc">USDT → USDC</option>
|
||||
<option value="usdc_to_usdt">USDC → USDT</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="convertAmt">数量</label>
|
||||
<input
|
||||
id="convertAmt"
|
||||
type="number"
|
||||
min={0}
|
||||
step="0.01"
|
||||
value={convertAmt}
|
||||
onChange={(e) => setConvertAmt(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-actions">
|
||||
<button
|
||||
className="btn"
|
||||
type="button"
|
||||
disabled={loading}
|
||||
onClick={async () => {
|
||||
setFundsErr("");
|
||||
setFundsOk("");
|
||||
setLoading(true);
|
||||
try {
|
||||
await apiFetch("/api/funds/convert", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
direction: convertDir,
|
||||
amount: convertAmt,
|
||||
}),
|
||||
});
|
||||
setFundsOk("兑换成功,顶部资金条将刷新");
|
||||
} catch (e) {
|
||||
setFundsErr(e instanceof Error ? e.message : String(e));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
兑换
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="settings-section">
|
||||
<h3>账户划转</h3>
|
||||
<p className="meta" style={{ marginTop: 0 }}>
|
||||
资金账户 ↔ 交易账户(USDT/USDC)。期权侧可用 options_funding /
|
||||
options_trading。
|
||||
</p>
|
||||
<div className="settings-fields">
|
||||
<div className="field">
|
||||
<label htmlFor="xferCcy">币种</label>
|
||||
<select
|
||||
id="xferCcy"
|
||||
value={xferCcy}
|
||||
onChange={(e) =>
|
||||
setXferCcy(e.target.value === "USDT" ? "USDT" : "USDC")
|
||||
}
|
||||
>
|
||||
<option value="USDC">USDC</option>
|
||||
<option value="USDT">USDT</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="xferFrom">从</label>
|
||||
<select
|
||||
id="xferFrom"
|
||||
value={xferFrom}
|
||||
onChange={(e) => setXferFrom(e.target.value)}
|
||||
>
|
||||
<option value="funding">资金账户</option>
|
||||
<option value="trading">交易账户</option>
|
||||
<option value="options_funding">期权资金</option>
|
||||
<option value="options_trading">期权交易</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="xferTo">到</label>
|
||||
<select
|
||||
id="xferTo"
|
||||
value={xferTo}
|
||||
onChange={(e) => setXferTo(e.target.value)}
|
||||
>
|
||||
<option value="funding">资金账户</option>
|
||||
<option value="trading">交易账户</option>
|
||||
<option value="options_funding">期权资金</option>
|
||||
<option value="options_trading">期权交易</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="xferAmt">数量</label>
|
||||
<input
|
||||
id="xferAmt"
|
||||
type="number"
|
||||
min={0}
|
||||
step="0.01"
|
||||
value={xferAmt}
|
||||
onChange={(e) => setXferAmt(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-actions">
|
||||
<button
|
||||
className="btn"
|
||||
type="button"
|
||||
disabled={loading}
|
||||
onClick={async () => {
|
||||
setFundsErr("");
|
||||
setFundsOk("");
|
||||
setLoading(true);
|
||||
try {
|
||||
await apiFetch("/api/funds/transfer", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
ccy: xferCcy,
|
||||
amount: xferAmt,
|
||||
from_account: xferFrom,
|
||||
to_account: xferTo,
|
||||
}),
|
||||
});
|
||||
setFundsOk("划转成功,顶部资金条将刷新");
|
||||
} catch (e) {
|
||||
setFundsErr(e instanceof Error ? e.message : String(e));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
划转
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{tab === "account" ? (
|
||||
<div className="card settings-card settings-card-narrow">
|
||||
{err ? <div className="err">{err}</div> : null}
|
||||
|
||||
Reference in New Issue
Block a user