Support LIVE exchange funds and perp margin mode.

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>
This commit is contained in:
dekun
2026-07-29 19:57:09 +08:00
parent 50a4540aaf
commit c6e8f6fe1e
12 changed files with 134 additions and 24 deletions
+2
View File
@@ -228,6 +228,7 @@ export type PlanState = {
premium_exit_multiple: number;
exit_target_usdt: number;
leverage: number;
perp_margin_mode?: "cross" | "isolated";
perp_qty_eth?: number;
option_qty_eth?: number;
min_option_hours: number;
@@ -311,6 +312,7 @@ export type StrategySettings = {
skip_weekends?: boolean;
initial_equity?: number;
leverage?: number;
perp_margin_mode?: "cross" | "isolated";
min_option_hours?: number;
min_option_leverage?: number;
atm_open_offset_enabled?: boolean;
+26 -5
View File
@@ -4,6 +4,7 @@ import { apiFetch } from "../api/client";
export type FundsSummary = {
ok: boolean;
exchange: string;
mode?: string;
trading_day: string;
total_trades: number;
win_rate: number;
@@ -53,22 +54,42 @@ function DualCcy({
export default function FundsBar() {
const [s, setS] = useState<FundsSummary | null>(null);
const [err, setErr] = useState("");
const load = useCallback(() => {
apiFetch<FundsSummary>("/api/funds/summary")
.then(setS)
.catch(() => {
/* 顶栏静默失败,避免刷屏 */
.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);
return () => window.clearInterval(t);
const onRefresh = () => load();
window.addEventListener("funds-refresh", onRefresh);
return () => {
window.clearInterval(t);
window.removeEventListener("funds-refresh", onRefresh);
};
}, [load]);
if (!s?.ok) return null;
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">
+34 -4
View File
@@ -71,6 +71,9 @@ export default function SettingsPage() {
const [orderInterval, setOrderInterval] = useState(1);
const [skipWeekends, setSkipWeekends] = useState(true);
const [leverage, setLeverage] = useState(3);
const [perpMarginMode, setPerpMarginMode] = useState<"cross" | "isolated">(
"cross",
);
const [minHours, setMinHours] = useState(12);
const [minOptLev, setMinOptLev] = useState(100);
const [atmOffOn, setAtmOffOn] = useState(false);
@@ -157,6 +160,9 @@ export default function SettingsPage() {
setOrderInterval(s.live_order_interval_sec ?? 1);
setSkipWeekends(s.skip_weekends !== false);
setLeverage(s.leverage ?? 3);
setPerpMarginMode(
s.perp_margin_mode === "isolated" ? "isolated" : "cross",
);
setMinHours(s.min_option_hours ?? 12);
setMinOptLev(s.min_option_leverage ?? 100);
setAtmOffOn(s.atm_open_offset_enabled === true);
@@ -292,7 +298,7 @@ export default function SettingsPage() {
e.preventDefault();
setStratOk("");
setErr("");
const isLive = (runtime?.mode ?? mode) === "LIVE";
const isLive = mode === "LIVE";
try {
const body: Record<string, unknown> = {
fee_rate: fee,
@@ -303,6 +309,7 @@ export default function SettingsPage() {
live_order_interval_sec: orderInterval,
skip_weekends: skipWeekends,
leverage,
perp_margin_mode: perpMarginMode,
min_option_hours: minHours,
min_option_leverage: minOptLev,
atm_open_offset_enabled: atmOffOn,
@@ -375,6 +382,7 @@ export default function SettingsPage() {
: `已切 LIVE,但未就绪:${r.live_ready_reason}`
: "已切换 SIM,配置已写入 .env",
);
window.dispatchEvent(new Event("funds-refresh"));
} catch (ex) {
setErr(ex instanceof Error ? ex.message : String(ex));
} finally {
@@ -531,7 +539,7 @@ export default function SettingsPage() {
</option>
</select>
</div>
{(runtime?.mode ?? mode) !== "LIVE" ? (
{mode !== "LIVE" ? (
<div className="field">
<label htmlFor="equity">USDT</label>
<input
@@ -559,6 +567,22 @@ export default function SettingsPage() {
onChange={(e) => setLeverage(Number(e.target.value))}
/>
</div>
<div className="field">
<label htmlFor="perpMm"></label>
<select
id="perpMm"
className="mono"
value={perpMarginMode}
onChange={(e) =>
setPerpMarginMode(
e.target.value === "isolated" ? "isolated" : "cross",
)
}
>
<option value="cross"></option>
<option value="isolated"></option>
</select>
</div>
<div className="field">
<label htmlFor="perp"> ETH </label>
<input
@@ -822,19 +846,23 @@ export default function SettingsPage() {
>
<ul className="settings-rules-list">
<li>
{(runtime?.mode ?? mode) === "LIVE"
{mode === "LIVE"
? "当前 LIVE:仓位/选约/出场影响真下单;模拟资金已隐藏。"
: "当前 SIM:本地撮合;可改模拟资金与交易所行情源。"}
</li>
{stratSub === "position" ? (
<>
<li></li>
{(runtime?.mode ?? mode) !== "LIVE" ? (
{mode !== "LIVE" ? (
<li>
SIM
10000
</li>
) : null}
<li>
/OKX
/cash
</li>
</>
) : null}
{stratSub === "select" ? (
@@ -1168,6 +1196,7 @@ export default function SettingsPage() {
}),
});
setFundsOk("兑换成功,顶部资金条将刷新");
window.dispatchEvent(new Event("funds-refresh"));
} catch (e) {
setFundsErr(e instanceof Error ? e.message : String(e));
} finally {
@@ -1257,6 +1286,7 @@ export default function SettingsPage() {
}),
});
setFundsOk("划转成功,顶部资金条将刷新");
window.dispatchEvent(new Event("funds-refresh"));
} catch (e) {
setFundsErr(e instanceof Error ? e.message : String(e));
} finally {