Implement P1 local matcher/ledger and P2 strategy engine.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+153
-56
@@ -1,5 +1,11 @@
|
||||
import { FormEvent, useState } from "react";
|
||||
import { changeCredentials, getUsername, setSession } from "../api/client";
|
||||
import { FormEvent, useEffect, useState } from "react";
|
||||
import {
|
||||
changeCredentials,
|
||||
getUsername,
|
||||
setSession,
|
||||
apiFetch,
|
||||
StrategySettings,
|
||||
} from "../api/client";
|
||||
|
||||
export default function SettingsPage() {
|
||||
const [newUsername, setNewUsername] = useState(getUsername() || "admin");
|
||||
@@ -10,7 +16,24 @@ export default function SettingsPage() {
|
||||
const [ok, setOk] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
async function onSave(e: FormEvent) {
|
||||
const [fee, setFee] = useState(0.0005);
|
||||
const [exitPts, setExitPts] = useState(30);
|
||||
const [rest, setRest] = useState(300);
|
||||
const [maxRounds, setMaxRounds] = useState(3);
|
||||
const [stratOk, setStratOk] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
apiFetch<StrategySettings>("/api/settings/strategy")
|
||||
.then((s) => {
|
||||
setFee(s.fee_rate);
|
||||
setExitPts(s.exit_move_points);
|
||||
setRest(s.rest_seconds);
|
||||
setMaxRounds(s.max_rounds);
|
||||
})
|
||||
.catch(() => undefined);
|
||||
}, []);
|
||||
|
||||
async function onSaveCreds(e: FormEvent) {
|
||||
e.preventDefault();
|
||||
setErr("");
|
||||
setOk("");
|
||||
@@ -41,60 +64,134 @@ export default function SettingsPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function onSaveStrategy(e: FormEvent) {
|
||||
e.preventDefault();
|
||||
setStratOk("");
|
||||
setErr("");
|
||||
try {
|
||||
await apiFetch("/api/settings/strategy", {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({
|
||||
fee_rate: fee,
|
||||
exit_move_points: exitPts,
|
||||
rest_seconds: rest,
|
||||
max_rounds: maxRounds,
|
||||
}),
|
||||
});
|
||||
setStratOk("策略参数已保存");
|
||||
} catch (ex) {
|
||||
setErr(ex instanceof Error ? ex.message : String(ex));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="card" style={{ maxWidth: 560 }}>
|
||||
<h2 style={{ marginTop: 0 }}>系统设置</h2>
|
||||
<p style={{ color: "var(--muted)" }}>修改登录用户名与密码(写入服务器 .env,立即生效)。</p>
|
||||
{err ? <div className="err">{err}</div> : null}
|
||||
{ok ? <div style={{ color: "var(--up)", marginBottom: 12 }}>{ok}</div> : null}
|
||||
<form onSubmit={onSave}>
|
||||
<div className="field">
|
||||
<label htmlFor="user">新用户名</label>
|
||||
<input
|
||||
id="user"
|
||||
value={newUsername}
|
||||
onChange={(e) => setNewUsername(e.target.value)}
|
||||
autoComplete="username"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="cur">当前密码</label>
|
||||
<input
|
||||
id="cur"
|
||||
type="password"
|
||||
value={currentPassword}
|
||||
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||
autoComplete="current-password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="np">新密码</label>
|
||||
<input
|
||||
id="np"
|
||||
type="password"
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
autoComplete="new-password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="cp">确认新密码</label>
|
||||
<input
|
||||
id="cp"
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
autoComplete="new-password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button className="btn" type="submit" disabled={loading}>
|
||||
{loading ? "保存中…" : "保存"}
|
||||
</button>
|
||||
</form>
|
||||
<div style={{ display: "grid", gap: 16, maxWidth: 560 }}>
|
||||
<div className="card">
|
||||
<h2 style={{ marginTop: 0 }}>策略设置</h2>
|
||||
<p style={{ color: "var(--muted)" }}>
|
||||
标的波动 N 点全平、轮次休息、费率(滑点=1×费率)。
|
||||
</p>
|
||||
{stratOk ? <div style={{ color: "var(--up)", marginBottom: 12 }}>{stratOk}</div> : null}
|
||||
<form onSubmit={onSaveStrategy}>
|
||||
<div className="field">
|
||||
<label htmlFor="exit">EXIT_MOVE_POINTS(标的波动点数)</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">REST_SECONDS(轮间休息秒)</label>
|
||||
<input
|
||||
id="rest"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="1"
|
||||
value={rest}
|
||||
onChange={(e) => setRest(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="rounds">MAX_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">FEE_RATE</label>
|
||||
<input
|
||||
id="fee"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="0.0001"
|
||||
value={fee}
|
||||
onChange={(e) => setFee(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<button className="btn" type="submit">
|
||||
保存策略
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<h2 style={{ marginTop: 0 }}>登录账号</h2>
|
||||
{err ? <div className="err">{err}</div> : null}
|
||||
{ok ? <div style={{ color: "var(--up)", marginBottom: 12 }}>{ok}</div> : null}
|
||||
<form onSubmit={onSaveCreds}>
|
||||
<div className="field">
|
||||
<label htmlFor="user">新用户名</label>
|
||||
<input
|
||||
id="user"
|
||||
value={newUsername}
|
||||
onChange={(e) => setNewUsername(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="cur">当前密码</label>
|
||||
<input
|
||||
id="cur"
|
||||
type="password"
|
||||
value={currentPassword}
|
||||
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="np">新密码</label>
|
||||
<input
|
||||
id="np"
|
||||
type="password"
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="cp">确认新密码</label>
|
||||
<input
|
||||
id="cp"
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button className="btn" type="submit" disabled={loading}>
|
||||
{loading ? "保存中…" : "保存账号"}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user