Add dual exit modes: fixed USDT or premium multiple.

Net PnL (after estimated close fees) drives auto close; Plan/Settings expose the choice.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-25 09:17:50 +08:00
parent 9fd2a842af
commit cce26e87b5
13 changed files with 282 additions and 72 deletions
+21 -8
View File
@@ -75,15 +75,20 @@ export default function PlanPage() {
const pos = plan?.position;
const open = !!pos?.has_position;
const exitPct = plan?.exit_move_pct ?? 2;
const movePct = pos?.move_pct ?? 0;
const exitMode = plan?.exit_mode ?? "fixed_usdt";
const exitTarget = plan?.exit_target_usdt ?? plan?.net_profit_target ?? 15;
const netPnl = pos?.net_pnl ?? 0;
const exitRuleLabel =
exitMode === "premium_multiple"
? `权利金×${fmt(plan?.premium_exit_multiple ?? 1, 2)}`
: `固定 ${fmt(plan?.net_profit_target ?? 15)} U`;
const phaseLabel = PHASE_ZH[plan?.phase || ""] || plan?.phase || "—";
return (
<div>
<h2 style={{ marginTop: 0 }}></h2>
<p style={{ color: "var(--muted)", marginTop: -8 }}>
SIM · · ·
SIM · · ·
</p>
{err ? <div className="err">{err}</div> : null}
@@ -174,17 +179,25 @@ export default function PlanPage() {
<span className="mono">{biasTag}</span>
</div>
<div className="kv">
<span> / </span>
<span></span>
<span className="mono">{exitRuleLabel}</span>
</div>
<div className="kv">
<span> / </span>
<span className="mono">
<span className={pnlClass(pos?.perp_upl)}>{fmt(pos?.perp_upl)}</span>
<span className={pnlClass(open ? netPnl : null)}>
{open ? fmt(netPnl) : "—"}
</span>
{" / "}
{fmt(pos?.premium_gap)}
{open || exitMode === "fixed_usdt" ? fmt(exitTarget) : "—"}
</span>
</div>
<div className="kv">
<span></span>
<span> / </span>
<span className="mono">
{fmt(movePct, 2)}% / {fmt(exitPct, 2)}%
<span className={pnlClass(pos?.perp_upl)}>{fmt(pos?.perp_upl)}</span>
{" / "}
<span className={pnlClass(pos?.option_upl)}>{fmt(pos?.option_upl)}</span>
</span>
</div>
{plan?.last_error ? (
+54 -13
View File
@@ -21,7 +21,11 @@ export default function SettingsPage() {
const [loading, setLoading] = useState(false);
const [fee, setFee] = useState(0.0005);
const [exitPct, setExitPct] = useState(2);
const [exitMode, setExitMode] = useState<"fixed_usdt" | "premium_multiple">(
"fixed_usdt",
);
const [netTarget, setNetTarget] = useState(15);
const [premMult, setPremMult] = useState(1);
const [rest, setRest] = useState(300);
const [leverage, setLeverage] = useState(3);
const [minHours, setMinHours] = useState(12);
@@ -35,7 +39,9 @@ export default function SettingsPage() {
apiFetch<StrategySettings>("/api/settings/strategy")
.then((s) => {
setFee(s.fee_rate);
setExitPct(s.exit_move_pct ?? 2);
setExitMode(s.exit_mode === "premium_multiple" ? "premium_multiple" : "fixed_usdt");
setNetTarget(s.net_profit_target ?? 15);
setPremMult(s.premium_exit_multiple ?? 1);
setRest(s.rest_seconds);
setLeverage(s.leverage ?? 3);
setMinHours(s.min_option_hours ?? 12);
@@ -87,7 +93,9 @@ export default function SettingsPage() {
method: "PUT",
body: JSON.stringify({
fee_rate: fee,
exit_move_pct: exitPct,
exit_mode: exitMode,
net_profit_target: netTarget,
premium_exit_multiple: premMult,
rest_seconds: rest,
leverage,
min_option_hours: minHours,
@@ -126,7 +134,7 @@ export default function SettingsPage() {
{tab === "strategy" ? (
<div className="card">
<p style={{ color: "var(--muted)", marginTop: 0 }}>
</p>
{stratOk ? <div style={{ color: "var(--up)", marginBottom: 12 }}>{stratOk}</div> : null}
{err && tab === "strategy" ? <div className="err">{err}</div> : null}
@@ -171,17 +179,50 @@ export default function SettingsPage() {
/>
</div>
<div className="field">
<label htmlFor="exit">%</label>
<input
id="exit"
<label htmlFor="exitMode"></label>
<select
id="exitMode"
className="mono"
type="number"
step="0.1"
min="0.1"
value={exitPct}
onChange={(e) => setExitPct(Number(e.target.value))}
/>
value={exitMode}
onChange={(e) =>
setExitMode(
e.target.value === "premium_multiple"
? "premium_multiple"
: "fixed_usdt",
)
}
>
<option value="fixed_usdt">USDT</option>
<option value="premium_multiple"></option>
</select>
</div>
{exitMode === "fixed_usdt" ? (
<div className="field">
<label htmlFor="netTarget">USDT</label>
<input
id="netTarget"
className="mono"
type="number"
step="0.1"
min="0.1"
value={netTarget}
onChange={(e) => setNetTarget(Number(e.target.value))}
/>
</div>
) : (
<div className="field">
<label htmlFor="premMult">1 = </label>
<input
id="premMult"
className="mono"
type="number"
step="0.1"
min="0.1"
value={premMult}
onChange={(e) => setPremMult(Number(e.target.value))}
/>
</div>
)}
<div className="field">
<label htmlFor="closeDev">/%</label>
<input