diff --git a/backend/app/strategy/engine.py b/backend/app/strategy/engine.py index 7e994da..4f66969 100644 --- a/backend/app/strategy/engine.py +++ b/backend/app/strategy/engine.py @@ -40,6 +40,11 @@ class StrategyEngine: rest_left = 0 if rest_until: rest_left = max(0, int((int(rest_until) - time.time() * 1000) / 1000)) + last_error = row["last_error"] + # 清掉已修复的旧序列化错误残留 + if last_error and "PriceResult" in str(last_error) and "__dict__" in str(last_error): + self._set_state(last_error=None) + last_error = None return { "running": bool(row["running"]), "phase": row["phase"], @@ -51,7 +56,7 @@ class StrategyEngine: "rest_seconds": rest_sec, "exit_move_points": exit_pts, "can_open": can_open_new(open_hhmm=s.open_hhmm, stop_hhmm=s.stop_open_hhmm), - "last_error": row["last_error"], + "last_error": last_error, "position": upl, "ledger": self.ledger.snapshot(), } @@ -67,16 +72,16 @@ class StrategyEngine: sql = f"UPDATE strategy_state SET {', '.join(cols)} WHERE id=1" self.db.execute(sql, tuple(vals)) + async def pause(self) -> dict[str, Any]: + self._set_state(running=0, phase="paused", last_error=None) + return self.state() + async def start(self) -> dict[str, Any]: self._set_state(running=1, last_error=None, phase="idle") if self._task is None or self._task.done(): self._task = asyncio.create_task(self._loop(), name="strategy-engine") return self.state() - async def pause(self) -> dict[str, Any]: - self._set_state(running=0, phase="paused") - return self.state() - async def emergency_close(self) -> dict[str, Any]: async with self._lock: r = self.matcher.close_group(reason="emergency") diff --git a/frontend/index.html b/frontend/index.html index 29e1315..dc4bf04 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,7 +3,7 @@ - eth_hedge_sim + OKX 自动化对冲系统 - +
+
+
OKX 自动化对冲系统
+
+ +
+ {user} + +
+
{children}
); diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 3fa8b11..a3dc06c 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -27,7 +27,7 @@ export default function LoginPage() { return (
-

eth_hedge_sim

+

OKX 自动化对冲系统

模拟盘登录

{err ?
{err}
: null}
diff --git a/frontend/src/pages/Plan.tsx b/frontend/src/pages/Plan.tsx index 721cf26..0bacee9 100644 --- a/frontend/src/pages/Plan.tsx +++ b/frontend/src/pages/Plan.tsx @@ -90,7 +90,7 @@ export default function PlanPage() { disabled={!!busy} onClick={() => act("/api/sim/open-group", "open")} > - 模拟开一组 + 手动开一组
diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index c9d1acb..34e2096 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -7,7 +7,11 @@ import { StrategySettings, } from "../api/client"; +type Tab = "strategy" | "account"; + export default function SettingsPage() { + const [tab, setTab] = useState("strategy"); + const [newUsername, setNewUsername] = useState(getUsername() || "admin"); const [currentPassword, setCurrentPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); @@ -85,113 +89,132 @@ export default function SettingsPage() { } return ( -
-
-

策略设置

-

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

- {stratOk ?
{stratOk}
: null} - -
- - setExitPts(Number(e.target.value))} - /> -
-
- - setRest(Number(e.target.value))} - /> -
-
- - setMaxRounds(Number(e.target.value))} - /> -
-
- - setFee(Number(e.target.value))} - /> -
- - +
+

系统设置

+
+ +
-
-

登录账号

- {err ?
{err}
: null} - {ok ?
{ok}
: null} -
-
- - setNewUsername(e.target.value)} - required - /> -
-
- - setCurrentPassword(e.target.value)} - required - /> -
-
- - setNewPassword(e.target.value)} - required - /> -
-
- - setConfirmPassword(e.target.value)} - required - /> -
- -
-
+ {tab === "strategy" ? ( +
+

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

+ {stratOk ?
{stratOk}
: null} + {err && tab === "strategy" ?
{err}
: null} +
+
+ + setExitPts(Number(e.target.value))} + /> +
+
+ + setRest(Number(e.target.value))} + /> +
+
+ + setMaxRounds(Number(e.target.value))} + /> +
+
+ + setFee(Number(e.target.value))} + /> +
+ +
+
+ ) : ( +
+ {err ?
{err}
: null} + {ok ?
{ok}
: null} +
+
+ + setNewUsername(e.target.value)} + required + /> +
+
+ + setCurrentPassword(e.target.value)} + required + /> +
+
+ + setNewPassword(e.target.value)} + required + /> +
+
+ + setConfirmPassword(e.target.value)} + required + /> +
+ +
+
+ )}
); } diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index fc864ce..5a0a497 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -49,7 +49,8 @@ input { } .topnav { - display: flex; + display: grid; + grid-template-columns: 1fr auto 1fr; align-items: center; gap: 8px; padding: 10px 16px; @@ -61,11 +62,31 @@ input { z-index: 10; } +.topnav-side { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; +} + +.topnav-side.right { + justify-content: flex-end; +} + +.topnav-center { + display: flex; + align-items: center; + justify-content: center; + gap: 4px; +} + .brand { font-weight: 700; letter-spacing: 0.02em; - margin-right: 12px; color: var(--accent); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .topnav a { @@ -73,6 +94,7 @@ input { text-decoration: none; padding: 8px 12px; border-radius: 6px; + white-space: nowrap; } .topnav a.active { @@ -80,6 +102,38 @@ input { background: var(--bg-panel); } +.tabs { + display: flex; + gap: 4px; + margin-bottom: 12px; + border-bottom: 1px solid var(--line); + padding-bottom: 0; +} + +.tab { + background: transparent; + border: 0; + color: var(--muted); + padding: 10px 14px; + cursor: pointer; + border-bottom: 2px solid transparent; + margin-bottom: -1px; +} + +.tab.active { + color: var(--text); + border-bottom-color: var(--accent); +} + +.status-running { + color: var(--up); + font-weight: 600; +} + +.status-stopped { + color: var(--muted); +} + .topnav .spacer { flex: 1; }