Fix running status color; add ETH qty settings with Chinese labels.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+36
-33
@@ -12,7 +12,15 @@ from .auth import require_user
|
|||||||
|
|
||||||
router = APIRouter(prefix="/api/settings", tags=["settings"])
|
router = APIRouter(prefix="/api/settings", tags=["settings"])
|
||||||
|
|
||||||
KEYS = ("fee_rate", "exit_move_points", "rest_seconds", "max_rounds", "initial_equity")
|
KEYS = (
|
||||||
|
"fee_rate",
|
||||||
|
"exit_move_points",
|
||||||
|
"rest_seconds",
|
||||||
|
"max_rounds",
|
||||||
|
"initial_equity",
|
||||||
|
"perp_qty_eth",
|
||||||
|
"option_qty_eth",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StrategySettingsBody(BaseModel):
|
class StrategySettingsBody(BaseModel):
|
||||||
@@ -21,42 +29,13 @@ class StrategySettingsBody(BaseModel):
|
|||||||
rest_seconds: int | None = Field(default=None, ge=0, le=3600)
|
rest_seconds: int | None = Field(default=None, ge=0, le=3600)
|
||||||
max_rounds: int | None = Field(default=None, ge=1, le=20)
|
max_rounds: int | None = Field(default=None, ge=1, le=20)
|
||||||
initial_equity: float | None = Field(default=None, ge=1000)
|
initial_equity: float | None = Field(default=None, ge=1000)
|
||||||
|
perp_qty_eth: float | None = Field(default=None, ge=0.01, le=100)
|
||||||
|
option_qty_eth: float | None = Field(default=None, ge=0.01, le=100)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/strategy")
|
def _read_settings() -> dict:
|
||||||
async def get_strategy_settings(_user: Annotated[str, Depends(require_user)]) -> dict:
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
out = {
|
|
||||||
"fee_rate": float(db.get_setting("fee_rate", str(s.fee_rate)) or s.fee_rate),
|
|
||||||
"exit_move_points": float(
|
|
||||||
db.get_setting("exit_move_points", str(s.exit_move_points)) or s.exit_move_points
|
|
||||||
),
|
|
||||||
"rest_seconds": int(
|
|
||||||
float(db.get_setting("rest_seconds", str(s.rest_seconds)) or s.rest_seconds)
|
|
||||||
),
|
|
||||||
"max_rounds": int(
|
|
||||||
float(db.get_setting("max_rounds", str(s.max_rounds)) or s.max_rounds)
|
|
||||||
),
|
|
||||||
"initial_equity": float(
|
|
||||||
db.get_setting("initial_equity", str(s.initial_equity)) or s.initial_equity
|
|
||||||
),
|
|
||||||
"ledger": Ledger(db).snapshot(),
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
@router.put("/strategy")
|
|
||||||
async def put_strategy_settings(
|
|
||||||
body: StrategySettingsBody,
|
|
||||||
_user: Annotated[str, Depends(require_user)],
|
|
||||||
) -> dict:
|
|
||||||
db = get_db()
|
|
||||||
data = body.model_dump(exclude_none=True)
|
|
||||||
for k, v in data.items():
|
|
||||||
if k in KEYS:
|
|
||||||
db.set_setting(k, str(v))
|
|
||||||
s = get_settings()
|
|
||||||
return {
|
return {
|
||||||
"fee_rate": float(db.get_setting("fee_rate", str(s.fee_rate)) or s.fee_rate),
|
"fee_rate": float(db.get_setting("fee_rate", str(s.fee_rate)) or s.fee_rate),
|
||||||
"exit_move_points": float(
|
"exit_move_points": float(
|
||||||
@@ -71,5 +50,29 @@ async def put_strategy_settings(
|
|||||||
"initial_equity": float(
|
"initial_equity": float(
|
||||||
db.get_setting("initial_equity", str(s.initial_equity)) or s.initial_equity
|
db.get_setting("initial_equity", str(s.initial_equity)) or s.initial_equity
|
||||||
),
|
),
|
||||||
|
"perp_qty_eth": float(
|
||||||
|
db.get_setting("perp_qty_eth", str(s.perp_qty_eth)) or s.perp_qty_eth
|
||||||
|
),
|
||||||
|
"option_qty_eth": float(
|
||||||
|
db.get_setting("option_qty_eth", str(s.option_qty_eth)) or s.option_qty_eth
|
||||||
|
),
|
||||||
"ledger": Ledger(db).snapshot(),
|
"ledger": Ledger(db).snapshot(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/strategy")
|
||||||
|
async def get_strategy_settings(_user: Annotated[str, Depends(require_user)]) -> dict:
|
||||||
|
return _read_settings()
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/strategy")
|
||||||
|
async def put_strategy_settings(
|
||||||
|
body: StrategySettingsBody,
|
||||||
|
_user: Annotated[str, Depends(require_user)],
|
||||||
|
) -> dict:
|
||||||
|
db = get_db()
|
||||||
|
data = body.model_dump(exclude_none=True)
|
||||||
|
for k, v in data.items():
|
||||||
|
if k in KEYS:
|
||||||
|
db.set_setting(k, str(v))
|
||||||
|
return _read_settings()
|
||||||
|
|||||||
@@ -151,6 +151,8 @@ class Database:
|
|||||||
"exit_move_points": str(s.exit_move_points),
|
"exit_move_points": str(s.exit_move_points),
|
||||||
"rest_seconds": str(s.rest_seconds),
|
"rest_seconds": str(s.rest_seconds),
|
||||||
"max_rounds": str(s.max_rounds),
|
"max_rounds": str(s.max_rounds),
|
||||||
|
"perp_qty_eth": str(s.perp_qty_eth),
|
||||||
|
"option_qty_eth": str(s.option_qty_eth),
|
||||||
}
|
}
|
||||||
for k, v in defaults.items():
|
for k, v in defaults.items():
|
||||||
exists = self._conn.execute(
|
exists = self._conn.execute(
|
||||||
|
|||||||
@@ -86,8 +86,9 @@ class Matcher:
|
|||||||
return OpenResult(ok=False, detail="期权卖一不可用")
|
return OpenResult(ok=False, detail="期权卖一不可用")
|
||||||
|
|
||||||
fee_rate = self._fee_rate()
|
fee_rate = self._fee_rate()
|
||||||
perp_qty = float(s.perp_qty_eth)
|
s = get_settings()
|
||||||
opt_qty = float(s.option_qty_eth)
|
perp_qty = self.ledger.get_setting_float("perp_qty_eth", s.perp_qty_eth)
|
||||||
|
opt_qty = self.ledger.get_setting_float("option_qty_eth", s.option_qty_eth)
|
||||||
ct_mult = self._ct_mult(option_inst_id)
|
ct_mult = self._ct_mult(option_inst_id)
|
||||||
opt_contracts = contracts_for_eth(opt_qty, ct_mult)
|
opt_contracts = contracts_for_eth(opt_qty, ct_mult)
|
||||||
|
|
||||||
|
|||||||
@@ -146,5 +146,7 @@ export type StrategySettings = {
|
|||||||
rest_seconds: number;
|
rest_seconds: number;
|
||||||
max_rounds: number;
|
max_rounds: number;
|
||||||
initial_equity: number;
|
initial_equity: number;
|
||||||
|
perp_qty_eth: number;
|
||||||
|
option_qty_eth: number;
|
||||||
ledger: { equity: number; available: number };
|
ledger: { equity: number; available: number };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,6 +59,20 @@ export default function PlanPage() {
|
|||||||
const exitN = plan?.exit_move_points ?? 30;
|
const exitN = plan?.exit_move_points ?? 30;
|
||||||
const move = pos?.move_points ?? 0;
|
const move = pos?.move_points ?? 0;
|
||||||
|
|
||||||
|
const phaseZh: Record<string, string> = {
|
||||||
|
idle: "空闲",
|
||||||
|
wait_signal: "等待信号",
|
||||||
|
opening: "开仓中",
|
||||||
|
open: "持仓中",
|
||||||
|
closing: "平仓中",
|
||||||
|
resting: "休息中",
|
||||||
|
paused: "已暂停",
|
||||||
|
stopped: "已停开",
|
||||||
|
outside_window: "窗外",
|
||||||
|
liquidity_wait: "流动性等待",
|
||||||
|
};
|
||||||
|
const phaseLabel = phaseZh[plan?.phase || ""] || plan?.phase || "—";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 style={{ marginTop: 0 }}>自动对冲计划</h2>
|
<h2 style={{ marginTop: 0 }}>自动对冲计划</h2>
|
||||||
@@ -114,13 +128,16 @@ export default function PlanPage() {
|
|||||||
<div className="card" style={{ marginBottom: 12 }}>
|
<div className="card" style={{ marginBottom: 12 }}>
|
||||||
<div className="kv">
|
<div className="kv">
|
||||||
<span>策略</span>
|
<span>策略</span>
|
||||||
<span className="mono">
|
<span>
|
||||||
{plan?.running ? (
|
{plan?.running ? (
|
||||||
<span className="status-running">启动中</span>
|
<span className="status-running">启动中</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="status-stopped">已停</span>
|
<span className="status-stopped">已停</span>
|
||||||
)}{" "}
|
)}
|
||||||
· {plan?.phase || "—"} · 轮次 {plan?.rounds_done ?? 0}/{plan?.max_rounds ?? 3}
|
<span className="mono">
|
||||||
|
{" "}
|
||||||
|
· {phaseLabel} · 轮次 {plan?.rounds_done ?? 0}/{plan?.max_rounds ?? 3}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="kv">
|
<div className="kv">
|
||||||
@@ -147,7 +164,7 @@ export default function PlanPage() {
|
|||||||
<span>方向</span>
|
<span>方向</span>
|
||||||
<span className="mono">
|
<span className="mono">
|
||||||
{pos?.has_position
|
{pos?.has_position
|
||||||
? `永续${pos.perp_side} + 买${pos.option_side?.toUpperCase()}`
|
? `永续${pos.perp_side === "long" ? "多" : "空"} + 买${pos.option_side === "call" ? "Call" : "Put"}`
|
||||||
: "—"}{" "}
|
: "—"}{" "}
|
||||||
{biasTag}
|
{biasTag}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export default function SettingsPage() {
|
|||||||
const [exitPts, setExitPts] = useState(30);
|
const [exitPts, setExitPts] = useState(30);
|
||||||
const [rest, setRest] = useState(300);
|
const [rest, setRest] = useState(300);
|
||||||
const [maxRounds, setMaxRounds] = useState(3);
|
const [maxRounds, setMaxRounds] = useState(3);
|
||||||
|
const [perpQty, setPerpQty] = useState(1);
|
||||||
|
const [optQty, setOptQty] = useState(2);
|
||||||
const [stratOk, setStratOk] = useState("");
|
const [stratOk, setStratOk] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -33,6 +35,8 @@ export default function SettingsPage() {
|
|||||||
setExitPts(s.exit_move_points);
|
setExitPts(s.exit_move_points);
|
||||||
setRest(s.rest_seconds);
|
setRest(s.rest_seconds);
|
||||||
setMaxRounds(s.max_rounds);
|
setMaxRounds(s.max_rounds);
|
||||||
|
setPerpQty(s.perp_qty_eth ?? 1);
|
||||||
|
setOptQty(s.option_qty_eth ?? 2);
|
||||||
})
|
})
|
||||||
.catch(() => undefined);
|
.catch(() => undefined);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -80,6 +84,8 @@ export default function SettingsPage() {
|
|||||||
exit_move_points: exitPts,
|
exit_move_points: exitPts,
|
||||||
rest_seconds: rest,
|
rest_seconds: rest,
|
||||||
max_rounds: maxRounds,
|
max_rounds: maxRounds,
|
||||||
|
perp_qty_eth: perpQty,
|
||||||
|
option_qty_eth: optQty,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
setStratOk("策略参数已保存");
|
setStratOk("策略参数已保存");
|
||||||
@@ -111,13 +117,37 @@ export default function SettingsPage() {
|
|||||||
{tab === "strategy" ? (
|
{tab === "strategy" ? (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<p style={{ color: "var(--muted)", marginTop: 0 }}>
|
<p style={{ color: "var(--muted)", marginTop: 0 }}>
|
||||||
标的波动 N 点全平、轮次休息、费率(滑点=1×费率)。
|
标的波动 N 点全平、轮次休息、仓位数量、费率(滑点=1×费率)。
|
||||||
</p>
|
</p>
|
||||||
{stratOk ? <div style={{ color: "var(--up)", marginBottom: 12 }}>{stratOk}</div> : null}
|
{stratOk ? <div style={{ color: "var(--up)", marginBottom: 12 }}>{stratOk}</div> : null}
|
||||||
{err && tab === "strategy" ? <div className="err">{err}</div> : null}
|
{err && tab === "strategy" ? <div className="err">{err}</div> : null}
|
||||||
<form onSubmit={onSaveStrategy}>
|
<form onSubmit={onSaveStrategy}>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label htmlFor="exit">EXIT_MOVE_POINTS(标的波动点数)</label>
|
<label htmlFor="perp">永续 ETH 数量</label>
|
||||||
|
<input
|
||||||
|
id="perp"
|
||||||
|
className="mono"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0.01"
|
||||||
|
value={perpQty}
|
||||||
|
onChange={(e) => setPerpQty(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="field">
|
||||||
|
<label htmlFor="opt">期权 ETH 数量</label>
|
||||||
|
<input
|
||||||
|
id="opt"
|
||||||
|
className="mono"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0.01"
|
||||||
|
value={optQty}
|
||||||
|
onChange={(e) => setOptQty(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="field">
|
||||||
|
<label htmlFor="exit">标的波动点数(全平阈值)</label>
|
||||||
<input
|
<input
|
||||||
id="exit"
|
id="exit"
|
||||||
className="mono"
|
className="mono"
|
||||||
@@ -128,7 +158,7 @@ export default function SettingsPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label htmlFor="rest">REST_SECONDS(轮间休息秒)</label>
|
<label htmlFor="rest">轮间休息秒数</label>
|
||||||
<input
|
<input
|
||||||
id="rest"
|
id="rest"
|
||||||
className="mono"
|
className="mono"
|
||||||
@@ -139,7 +169,7 @@ export default function SettingsPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label htmlFor="rounds">MAX_ROUNDS</label>
|
<label htmlFor="rounds">每日最大轮次</label>
|
||||||
<input
|
<input
|
||||||
id="rounds"
|
id="rounds"
|
||||||
className="mono"
|
className="mono"
|
||||||
@@ -150,7 +180,7 @@ export default function SettingsPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label htmlFor="fee">FEE_RATE</label>
|
<label htmlFor="fee">手续费率</label>
|
||||||
<input
|
<input
|
||||||
id="fee"
|
id="fee"
|
||||||
className="mono"
|
className="mono"
|
||||||
|
|||||||
@@ -126,12 +126,12 @@ input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.status-running {
|
.status-running {
|
||||||
color: var(--up);
|
color: #0ecb81 !important;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-stopped {
|
.status-stopped {
|
||||||
color: var(--muted);
|
color: var(--muted) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topnav .spacer {
|
.topnav .spacer {
|
||||||
|
|||||||
Reference in New Issue
Block a user