("/api/auth/change-credentials", {
method: "POST",
body: JSON.stringify(input),
});
+ setSession(res.token, res.username, res.expires_in);
+ return res;
}
export type MarketSnapshot = {
@@ -135,6 +234,7 @@ export type PlanState = {
max_atm_open_offset?: number;
can_open: boolean;
last_error: string | null;
+ show_manual_trade_buttons?: boolean;
position: {
has_position: boolean;
group_id?: string;
@@ -217,6 +317,7 @@ export type StrategySettings = {
close_bid_mark_max_pct: number;
perp_qty_eth: number;
option_qty_eth: number;
+ show_manual_trade_buttons?: boolean;
exchange: "okx" | "binance";
perp_inst_id?: string;
option_inst_family?: string;
diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx
index ac4499c..3f028cd 100644
--- a/frontend/src/pages/Login.tsx
+++ b/frontend/src/pages/Login.tsx
@@ -15,7 +15,7 @@ export default function LoginPage() {
setLoading(true);
try {
const res = await login(username.trim(), password);
- setSession(res.token, res.username);
+ setSession(res.token, res.username, res.expires_in);
nav("/plan", { replace: true });
} catch (ex) {
setErr(ex instanceof Error ? ex.message : String(ex));
diff --git a/frontend/src/pages/Plan.tsx b/frontend/src/pages/Plan.tsx
index c4f5f8a..8f295e1 100644
--- a/frontend/src/pages/Plan.tsx
+++ b/frontend/src/pages/Plan.tsx
@@ -144,11 +144,11 @@ export default function PlanPage() {
+ {plan?.show_manual_trade_buttons ? (
+ <>
+
+
+ >
+ ) : null}
-
-
+
+
+
+
) : null}
@@ -607,10 +624,16 @@ export default function SettingsPage() {
) : null}
{stratSub === "pace" ? (
-
- 实盘下单最小间隔:LIVE 私有下单/查单间隔,默认 1s。范围
- 0.2–30。
-
+ <>
+
+ 实盘下单最小间隔:LIVE 私有下单/查单间隔,默认 1s。范围
+ 0.2–30。
+
+
+ 显示手动开仓:默认关闭。开启后计划页才出现「手动开一组 /
+ 手动全平」。策略自动运行中禁止手动开仓(须先暂停);紧急全平始终可用。
+
+ >
) : null}
diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css
index da89e6e..737293d 100644
--- a/frontend/src/styles/app.css
+++ b/frontend/src/styles/app.css
@@ -613,6 +613,25 @@ input {
border: 1px solid var(--line);
}
+.btn.danger {
+ background: transparent;
+ color: var(--danger);
+ border: 1px solid var(--danger);
+}
+
+.btn.danger:hover:not(:disabled) {
+ background: rgba(246, 70, 93, 0.12);
+}
+
+.btn.btn-running,
+.btn.btn-running:disabled {
+ background: rgba(14, 203, 129, 0.16);
+ color: #0ecb81;
+ border: 1px solid #0ecb81;
+ opacity: 1;
+ cursor: default;
+}
+
.btn.block {
width: 100%;
}