diff --git a/backend/app/api/settings.py b/backend/app/api/settings.py index 4872ac9..3a2c49b 100644 --- a/backend/app/api/settings.py +++ b/backend/app/api/settings.py @@ -12,7 +12,15 @@ from .auth import require_user 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): @@ -21,42 +29,13 @@ class StrategySettingsBody(BaseModel): rest_seconds: int | None = Field(default=None, ge=0, le=3600) max_rounds: int | None = Field(default=None, ge=1, le=20) 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") -async def get_strategy_settings(_user: Annotated[str, Depends(require_user)]) -> dict: +def _read_settings() -> dict: db = get_db() 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 { "fee_rate": float(db.get_setting("fee_rate", str(s.fee_rate)) or s.fee_rate), "exit_move_points": float( @@ -71,5 +50,29 @@ async def put_strategy_settings( "initial_equity": float( 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(), } + + +@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() diff --git a/backend/app/models/db.py b/backend/app/models/db.py index bac8617..cbbd91e 100644 --- a/backend/app/models/db.py +++ b/backend/app/models/db.py @@ -151,6 +151,8 @@ class Database: "exit_move_points": str(s.exit_move_points), "rest_seconds": str(s.rest_seconds), "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(): exists = self._conn.execute( diff --git a/backend/app/sim/matcher.py b/backend/app/sim/matcher.py index a021cd0..9876512 100644 --- a/backend/app/sim/matcher.py +++ b/backend/app/sim/matcher.py @@ -86,8 +86,9 @@ class Matcher: return OpenResult(ok=False, detail="期权卖一不可用") fee_rate = self._fee_rate() - perp_qty = float(s.perp_qty_eth) - opt_qty = float(s.option_qty_eth) + s = get_settings() + 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) opt_contracts = contracts_for_eth(opt_qty, ct_mult) diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index d4750f8..db84811 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -146,5 +146,7 @@ export type StrategySettings = { rest_seconds: number; max_rounds: number; initial_equity: number; + perp_qty_eth: number; + option_qty_eth: number; ledger: { equity: number; available: number }; }; diff --git a/frontend/src/pages/Plan.tsx b/frontend/src/pages/Plan.tsx index 0bacee9..8b9c100 100644 --- a/frontend/src/pages/Plan.tsx +++ b/frontend/src/pages/Plan.tsx @@ -59,6 +59,20 @@ export default function PlanPage() { const exitN = plan?.exit_move_points ?? 30; const move = pos?.move_points ?? 0; + const phaseZh: Record = { + idle: "空闲", + wait_signal: "等待信号", + opening: "开仓中", + open: "持仓中", + closing: "平仓中", + resting: "休息中", + paused: "已暂停", + stopped: "已停开", + outside_window: "窗外", + liquidity_wait: "流动性等待", + }; + const phaseLabel = phaseZh[plan?.phase || ""] || plan?.phase || "—"; + return (

自动对冲计划

@@ -114,13 +128,16 @@ export default function PlanPage() {
策略 - + {plan?.running ? ( 启动中 ) : ( 已停 - )}{" "} - · {plan?.phase || "—"} · 轮次 {plan?.rounds_done ?? 0}/{plan?.max_rounds ?? 3} + )} + + {" "} + · {phaseLabel} · 轮次 {plan?.rounds_done ?? 0}/{plan?.max_rounds ?? 3} +
@@ -147,7 +164,7 @@ export default function PlanPage() { 方向 {pos?.has_position - ? `永续${pos.perp_side} + 买${pos.option_side?.toUpperCase()}` + ? `永续${pos.perp_side === "long" ? "多" : "空"} + 买${pos.option_side === "call" ? "Call" : "Put"}` : "—"}{" "} {biasTag} diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 34e2096..7209fb7 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -24,6 +24,8 @@ export default function SettingsPage() { const [exitPts, setExitPts] = useState(30); const [rest, setRest] = useState(300); const [maxRounds, setMaxRounds] = useState(3); + const [perpQty, setPerpQty] = useState(1); + const [optQty, setOptQty] = useState(2); const [stratOk, setStratOk] = useState(""); useEffect(() => { @@ -33,6 +35,8 @@ export default function SettingsPage() { setExitPts(s.exit_move_points); setRest(s.rest_seconds); setMaxRounds(s.max_rounds); + setPerpQty(s.perp_qty_eth ?? 1); + setOptQty(s.option_qty_eth ?? 2); }) .catch(() => undefined); }, []); @@ -80,6 +84,8 @@ export default function SettingsPage() { exit_move_points: exitPts, rest_seconds: rest, max_rounds: maxRounds, + perp_qty_eth: perpQty, + option_qty_eth: optQty, }), }); setStratOk("策略参数已保存"); @@ -111,13 +117,37 @@ export default function SettingsPage() { {tab === "strategy" ? (

- 标的波动 N 点全平、轮次休息、费率(滑点=1×费率)。 + 标的波动 N 点全平、轮次休息、仓位数量、费率(滑点=1×费率)。

{stratOk ?
{stratOk}
: null} {err && tab === "strategy" ?
{err}
: null}
- + + setPerpQty(Number(e.target.value))} + /> +
+
+ + setOptQty(Number(e.target.value))} + /> +
+
+
- +
- +
- +