Revise strategy: TTM+ATM+leverage option pick, % exit, perp leverage/margin.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-25 08:38:03 +08:00
parent 7db4c9c8a3
commit 3957a83761
16 changed files with 532 additions and 261 deletions
+12 -4
View File
@@ -117,11 +117,13 @@ export type PlanState = {
running: boolean;
phase: string;
rounds_done: number;
max_rounds: number;
window_key: string | null;
rest_left_sec: number;
rest_seconds: number;
exit_move_points: number;
exit_move_pct: number;
leverage: number;
min_option_hours: number;
min_option_leverage: number;
can_open: boolean;
last_error: string | null;
position: {
@@ -133,6 +135,9 @@ export type PlanState = {
perp_entry_px?: number;
perp_qty_eth?: number;
perp_mark_px?: number | null;
perp_notional?: number;
perp_margin?: number | null;
leverage?: number;
option_inst_id?: string;
option_entry_px?: number;
option_qty_eth?: number;
@@ -145,6 +150,7 @@ export type PlanState = {
index_px?: number | null;
entry_index_px?: number;
move_points?: number;
move_pct?: number;
initial_premium?: number;
premium_gap?: number;
};
@@ -153,10 +159,12 @@ export type PlanState = {
export type StrategySettings = {
fee_rate: number;
exit_move_points: number;
exit_move_pct: number;
rest_seconds: number;
max_rounds: number;
initial_equity: number;
leverage: number;
min_option_hours: number;
min_option_leverage: number;
perp_qty_eth: number;
option_qty_eth: number;
ledger: { equity: number; available: number };
+20 -16
View File
@@ -75,15 +75,15 @@ export default function PlanPage() {
const pos = plan?.position;
const open = !!pos?.has_position;
const exitN = plan?.exit_move_points ?? 30;
const move = pos?.move_points ?? 0;
const exitPct = plan?.exit_move_pct ?? 2;
const movePct = pos?.move_pct ?? 0;
const phaseLabel = PHASE_ZH[plan?.phase || ""] || plan?.phase || "—";
return (
<div>
<h2 style={{ marginTop: 0 }}>自动对冲计划</h2>
<p style={{ color: "var(--muted)", marginTop: -8 }}>
SIM 本地撮合 · 期权只买 · 标的波动 N 点可配
SIM 本地撮合 · 期权只买 · 剩余时长选到期 · 波动按比例全平
</p>
{err ? <div className="err">{err}</div> : null}
@@ -142,14 +142,14 @@ export default function PlanPage() {
)}
<span className="mono">
{" "}
· {phaseLabel} · 轮次 {plan?.rounds_done ?? 0}/{plan?.max_rounds ?? 3}
· {phaseLabel} · 已完成 {plan?.rounds_done ?? 0} 组
</span>
</span>
</div>
<div className="kv">
<span>开仓窗</span>
<span>选约条件</span>
<span className="mono">
{plan?.can_open ? "可开" : "禁止新开"} · 窗 {plan?.window_key || "—"}
剩余≥{fmt(plan?.min_option_hours, 0)}h · 期权杠杆≥{fmt(plan?.min_option_leverage, 0)}x
</span>
</div>
<div className="kv">
@@ -159,9 +159,10 @@ export default function PlanPage() {
</span>
</div>
<div className="kv">
<span>权益</span>
<span>权益 / 杠杆</span>
<span className="mono">
{fmt(plan?.ledger?.equity)} / 可用 {fmt(plan?.ledger?.available)}
{fmt(plan?.ledger?.equity)} / 可用 {fmt(plan?.ledger?.available)} ·{" "}
{fmt(plan?.leverage, 0)}x
</span>
</div>
<div className="kv">
@@ -181,9 +182,9 @@ export default function PlanPage() {
</span>
</div>
<div className="kv">
<span>N 点进度</span>
<span>波动进度</span>
<span className="mono">
{fmt(move, 1)} / {fmt(exitN, 0)}
{fmt(movePct, 2)}% / {fmt(exitPct, 2)}%
</span>
</div>
{plan?.last_error ? (
@@ -217,6 +218,9 @@ export default function PlanPage() {
<span className="pos-meta-item">永续持仓</span>
<span className="pos-meta-item">组 {pos?.group_id}</span>
<span className="pos-meta-item mono">数量 {fmt(pos?.perp_qty_eth, 2)} ETH</span>
<span className="pos-meta-item mono">
{fmt(pos?.leverage ?? plan?.leverage, 0)}x
</span>
</div>
<div className="pos-grid">
<div className="pos-cell">
@@ -234,16 +238,16 @@ export default function PlanPage() {
</span>
</div>
<div className="pos-cell">
<span className="pos-label">指数</span>
<span className="pos-value mono">{fmt(pos?.index_px)}</span>
<span className="pos-label">保证金</span>
<span className="pos-value mono">{fmt(pos?.perp_margin)}</span>
</div>
<div className="pos-cell">
<span className="pos-label">开仓指数</span>
<span className="pos-value mono">{fmt(pos?.entry_index_px)}</span>
<span className="pos-label">名义价值</span>
<span className="pos-value mono">{fmt(pos?.perp_notional)}</span>
</div>
<div className="pos-cell">
<span className="pos-label">波动点数</span>
<span className="pos-value mono">{fmt(move, 1)}</span>
<span className="pos-label">波动比例</span>
<span className="pos-value mono">{fmt(movePct, 2)}%</span>
</div>
</div>
</div>
+64 -29
View File
@@ -21,9 +21,11 @@ export default function SettingsPage() {
const [loading, setLoading] = useState(false);
const [fee, setFee] = useState(0.0005);
const [exitPts, setExitPts] = useState(30);
const [exitPct, setExitPct] = useState(2);
const [rest, setRest] = useState(300);
const [maxRounds, setMaxRounds] = useState(3);
const [leverage, setLeverage] = useState(3);
const [minHours, setMinHours] = useState(12);
const [minOptLev, setMinOptLev] = useState(100);
const [perpQty, setPerpQty] = useState(1);
const [optQty, setOptQty] = useState(2);
const [stratOk, setStratOk] = useState("");
@@ -32,9 +34,11 @@ export default function SettingsPage() {
apiFetch<StrategySettings>("/api/settings/strategy")
.then((s) => {
setFee(s.fee_rate);
setExitPts(s.exit_move_points);
setExitPct(s.exit_move_pct ?? 2);
setRest(s.rest_seconds);
setMaxRounds(s.max_rounds);
setLeverage(s.leverage ?? 3);
setMinHours(s.min_option_hours ?? 12);
setMinOptLev(s.min_option_leverage ?? 100);
setPerpQty(s.perp_qty_eth ?? 1);
setOptQty(s.option_qty_eth ?? 2);
})
@@ -81,9 +85,11 @@ export default function SettingsPage() {
method: "PUT",
body: JSON.stringify({
fee_rate: fee,
exit_move_points: exitPts,
exit_move_pct: exitPct,
rest_seconds: rest,
max_rounds: maxRounds,
leverage,
min_option_hours: minHours,
min_option_leverage: minOptLev,
perp_qty_eth: perpQty,
option_qty_eth: optQty,
}),
@@ -117,11 +123,62 @@ export default function SettingsPage() {
{tab === "strategy" ? (
<div className="card">
<p style={{ color: "var(--muted)", marginTop: 0 }}>
标的波动 N 点全平、轮次休息、仓位数量、费率(滑点=1×费率)。
无开仓时间窗;期权按剩余时长选到期 → 平值 → 校验杠杆;波动按百分比全平。
</p>
{stratOk ? <div style={{ color: "var(--up)", marginBottom: 12 }}>{stratOk}</div> : null}
{err && tab === "strategy" ? <div className="err">{err}</div> : null}
<form onSubmit={onSaveStrategy}>
<h3 style={{ margin: "0 0 10px", fontSize: 14, color: "var(--muted)" }}>资金</h3>
<div className="field">
<label htmlFor="lev">永续杠杆倍数</label>
<input
id="lev"
className="mono"
type="number"
step="1"
min="1"
value={leverage}
onChange={(e) => setLeverage(Number(e.target.value))}
/>
</div>
<h3 style={{ margin: "18px 0 10px", fontSize: 14, color: "var(--muted)" }}>策略</h3>
<div className="field">
<label htmlFor="hours">期权最小剩余小时</label>
<input
id="hours"
className="mono"
type="number"
step="1"
min="1"
value={minHours}
onChange={(e) => setMinHours(Number(e.target.value))}
/>
</div>
<div className="field">
<label htmlFor="olev">期权杠杆下限(现价÷卖一权利金)</label>
<input
id="olev"
className="mono"
type="number"
step="1"
min="1"
value={minOptLev}
onChange={(e) => setMinOptLev(Number(e.target.value))}
/>
</div>
<div className="field">
<label htmlFor="exit">波动出场比例(%)</label>
<input
id="exit"
className="mono"
type="number"
step="0.1"
min="0.1"
value={exitPct}
onChange={(e) => setExitPct(Number(e.target.value))}
/>
</div>
<div className="field">
<label htmlFor="perp">永续 ETH 数量</label>
<input
@@ -146,17 +203,6 @@ export default function SettingsPage() {
onChange={(e) => setOptQty(Number(e.target.value))}
/>
</div>
<div className="field">
<label htmlFor="exit">标的波动点数(全平阈值)</label>
<input
id="exit"
className="mono"
type="number"
step="1"
value={exitPts}
onChange={(e) => setExitPts(Number(e.target.value))}
/>
</div>
<div className="field">
<label htmlFor="rest">轮间休息秒数</label>
<input
@@ -168,17 +214,6 @@ export default function SettingsPage() {
onChange={(e) => setRest(Number(e.target.value))}
/>
</div>
<div className="field">
<label htmlFor="rounds">每日最大轮次</label>
<input
id="rounds"
className="mono"
type="number"
step="1"
value={maxRounds}
onChange={(e) => setMaxRounds(Number(e.target.value))}
/>
</div>
<div className="field">
<label htmlFor="fee">手续费率</label>
<input