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:
@@ -120,7 +120,11 @@ export type PlanState = {
|
||||
window_key: string | null;
|
||||
rest_left_sec: number;
|
||||
rest_seconds: number;
|
||||
exit_move_pct: number;
|
||||
exit_move_pct?: number;
|
||||
exit_mode: "fixed_usdt" | "premium_multiple";
|
||||
net_profit_target: number;
|
||||
premium_exit_multiple: number;
|
||||
exit_target_usdt: number;
|
||||
leverage: number;
|
||||
min_option_hours: number;
|
||||
min_option_leverage: number;
|
||||
@@ -147,6 +151,8 @@ export type PlanState = {
|
||||
expiry_ymd?: string | null;
|
||||
perp_upl?: number;
|
||||
option_upl?: number;
|
||||
est_close_fees?: number;
|
||||
net_pnl?: number;
|
||||
index_px?: number | null;
|
||||
entry_index_px?: number;
|
||||
move_points?: number;
|
||||
@@ -159,7 +165,10 @@ export type PlanState = {
|
||||
|
||||
export type StrategySettings = {
|
||||
fee_rate: number;
|
||||
exit_move_pct: number;
|
||||
exit_move_pct?: number;
|
||||
exit_mode: "fixed_usdt" | "premium_multiple";
|
||||
net_profit_target: number;
|
||||
premium_exit_multiple: number;
|
||||
rest_seconds: number;
|
||||
initial_equity: number;
|
||||
leverage: number;
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -225,7 +225,8 @@ input {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.field input {
|
||||
.field input,
|
||||
.field select {
|
||||
background: var(--input);
|
||||
border: 1px solid var(--line);
|
||||
color: var(--text);
|
||||
@@ -234,7 +235,8 @@ input {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.field input:focus {
|
||||
.field input:focus,
|
||||
.field select:focus {
|
||||
border-color: rgba(240, 185, 11, 0.55);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user