diff --git a/.cursor/rules/deliver-and-chat.mdc b/.cursor/rules/deliver-and-chat.mdc new file mode 100644 index 0000000..89f1cb0 --- /dev/null +++ b/.cursor/rules/deliver-and-chat.mdc @@ -0,0 +1,38 @@ +--- +description: 改完必推送部署;用户说「先聊」则只讨论不改代码 +alwaysApply: true +--- + +# 交付与对话模式(硬指令) + +## 先聊模式(最高优先) + +用户消息含 **「先聊」**(或明确说只讨论、不要改代码、先别动手)时: + +- **禁止**改代码、写文件、提交、推送、部署 +- 只做讨论、方案对比、审计结论、问答 +- 用户之后明确说「改吧 / 实现 / 动手」再进入改代码 + +## 改完必推送 + 部署 + +在**本仓库**完成一轮实际代码修改后(用户未说「先聊」、且不是纯问答),必须收尾: + +1. **提交**:有改动则 `git add` 相关文件并 `git commit`(勿提交 `.env`、密钥、本地临时脚本如 `scripts/_ssh_*`、`_t.py`) +2. **推送**:`git push origin HEAD`(或当前跟踪分支) +3. **部署**:用本机脚本更新服务器(git pull + 构建 + pm2),例如: + +```powershell +$env:PYTHONIOENCODING='utf-8' +# DEPLOY_PASS 须已在环境中配置;勿把密码写进规则或提交到仓库 +& .\.venv\Scripts\python.exe .\scripts\deploy_remote.py +``` + +- 服务器:`47.236.184.99`,目录 `/opt/eth_hedge_sim`,进程 `eth-hedge-api` +- 部署成功后简要回报:commit、push、health 是否 OK +- 若用户明确说「先别部署 / 只提交不推 / 只推不部署」,则按用户当次要求覆盖本条 + +## 例外(可不推送部署) + +- 「先聊」或只读审计、解释、方案讨论 +- 仅改本地未跟踪的临时探测脚本且用户未要求交付 +- 推送/部署失败时说明原因与已完成步骤,不要假装成功 diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 7568bd1..3ae2b43 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -9,9 +9,15 @@ import { } from "../api/client"; type Tab = "strategy" | "runtime" | "account"; +type StratSub = + | "position" + | "select" + | "exit" + | "pace"; export default function SettingsPage() { const [tab, setTab] = useState("strategy"); + const [stratSub, setStratSub] = useState("position"); const [newUsername, setNewUsername] = useState(getUsername() || "admin"); const [currentPassword, setCurrentPassword] = useState(""); @@ -120,31 +126,38 @@ export default function SettingsPage() { e.preventDefault(); setStratOk(""); setErr(""); + const isLive = (runtime?.mode ?? mode) === "LIVE"; try { + const body: Record = { + fee_rate: fee, + exit_mode: exitMode, + net_profit_target: netTarget, + premium_exit_multiple: premMult, + rest_seconds: rest, + live_order_interval_sec: orderInterval, + skip_weekends: skipWeekends, + leverage, + min_option_hours: minHours, + min_option_leverage: minOptLev, + atm_open_offset_enabled: atmOffOn, + max_atm_open_offset: maxAtmOff, + close_bid_mark_max_pct: closeDevPct, + perp_qty_eth: perpQty, + option_qty_eth: optQty, + exchange, + }; + // LIVE 不改模拟资金,避免误重置本地账本 + if (!isLive) { + body.initial_equity = initialEquity; + } await apiFetch("/api/settings/strategy", { method: "PUT", - body: JSON.stringify({ - fee_rate: fee, - exit_mode: exitMode, - net_profit_target: netTarget, - premium_exit_multiple: premMult, - rest_seconds: rest, - live_order_interval_sec: orderInterval, - skip_weekends: skipWeekends, - leverage, - min_option_hours: minHours, - min_option_leverage: minOptLev, - atm_open_offset_enabled: atmOffOn, - max_atm_open_offset: maxAtmOff, - close_bid_mark_max_pct: closeDevPct, - perp_qty_eth: perpQty, - option_qty_eth: optQty, - initial_equity: initialEquity, - exchange, - }), + body: JSON.stringify(body), }); setStratOk( - "策略参数已保存(切换交易所/改模拟资金需无持仓;切换后会重连行情)", + isLive + ? "策略参数已保存(实盘模式不改模拟资金)" + : "策略参数已保存(切换交易所/改模拟资金需无持仓;切换后会重连行情)", ); } catch (ex) { setErr(ex instanceof Error ? ex.message : String(ex)); @@ -206,7 +219,10 @@ export default function SettingsPage() { @@ -232,265 +248,318 @@ export default function SettingsPage() { {tab === "strategy" ? (

- 无开仓时间窗;可切换 OKX / 币安行情(SIM 本地撮合);周六日可跳过开仓;出场按净盈利或到期全平。 + {(runtime?.mode ?? mode) === "LIVE" + ? "当前 LIVE:仓位/选约/出场影响真下单;模拟资金已隐藏。" + : "当前 SIM:本地撮合;可改模拟资金与交易所行情源。"}

{stratOk ?
{stratOk}
: null} {err && tab === "strategy" ?
{err}
: null} + +
+ {( + [ + ["position", "仓位"], + ["select", "选约"], + ["exit", "出场"], + ["pace", "节奏"], + ] as const + ).map(([id, label]) => ( + + ))} +
+
-
-

交易所

-
-
- - -

- 有持仓时不可切换。切换后自动套用该所合约并重连公共行情。 -

-
-
-
- -
-

资金

-
-
- - setInitialEquity(Number(e.target.value))} - /> -

- 保存后将权益重置为该金额(须无持仓)。默认 10000。 -

-
-
- - setLeverage(Number(e.target.value))} - /> -
-
- - setPerpQty(Number(e.target.value))} - /> -
-
- - setOptQty(Number(e.target.value))} - /> -
-
-
- -
-

选约

-
-
- - setMinHours(Number(e.target.value))} - /> -
-
- - setMinOptLev(Number(e.target.value))} - /> -
-
- - -

- 关闭时不限制 |ATM − 现价|;开启后超过下方点数则不开仓。 -

-
-
- - setMaxAtmOff(Number(e.target.value))} - /> -

- 仅在上方开关开启时生效。默认 3;币安行权价步进较粗时可能长时间无开仓机会。 -

-
-
-
- -
-

出场与风控

-
-
- - -
- {exitMode === "fixed_usdt" ? ( + {stratSub === "position" ? ( +
+

交易所与仓位

+
- + + +

+ 有持仓时不可切换。切换后套用该所合约并重连行情。 +

+
+ {(runtime?.mode ?? mode) !== "LIVE" ? ( +
+ + + setInitialEquity(Number(e.target.value)) + } + /> +

+ 仅 SIM 显示。保存后重置权益(须无持仓)。默认 10000。 +

+
+ ) : null} +
+ setLeverage(Number(e.target.value))} + /> +
+
+ + setPerpQty(Number(e.target.value))} + /> +
+
+ + setOptQty(Number(e.target.value))} + /> +
+
+
+ ) : null} + + {stratSub === "select" ? ( +
+

选约条件

+
+
+ + setMinHours(Number(e.target.value))} + /> +
+
+ + setMinOptLev(Number(e.target.value))} + /> +
+
+ + +

+ 关闭时不限制 |ATM − 现价|;开启后超过下方点数则不开仓。 +

+
+
+ + setMaxAtmOff(Number(e.target.value))} + /> +

+ 仅开关开启时生效。币安行权价较粗时可能长时间无开仓机会。 +

+
+
+
+ ) : null} + + {stratSub === "exit" ? ( +
+

出场与平仓

+
+
+ + +
+ {exitMode === "fixed_usdt" ? ( +
+ + setNetTarget(Number(e.target.value))} + /> +
+ ) : ( +
+ + setPremMult(Number(e.target.value))} + /> +
+ )} +
+ + setCloseDevPct(Number(e.target.value))} + /> +

+ 主要用于 SIM 流动性闸门;LIVE 以交易所能否成交为准。 +

+
+
+
+ ) : null} + + {stratSub === "pace" ? ( +
+

节奏与费用

+
+
+ + setRest(Number(e.target.value))} + /> +
+
+ + setNetTarget(Number(e.target.value))} + min="0.2" + max="30" + value={orderInterval} + onChange={(e) => + setOrderInterval(Number(e.target.value)) + } /> +

+ LIVE 私有下单/查单间隔,默认 1s。范围 0.2–30。 +

- ) : (
- + + +
+
+ setPremMult(Number(e.target.value))} + step="0.0001" + value={fee} + onChange={(e) => setFee(Number(e.target.value))} />
- )} -
- - setCloseDevPct(Number(e.target.value))} - />
-
- - setRest(Number(e.target.value))} - /> -
-
- - setOrderInterval(Number(e.target.value))} - /> -

- LIVE 私有下单/查单间隔,默认 1s(非高频建议 ≥1)。范围 - 0.2–30。保存后立即生效。 -

-
-
- - -
-
- - setFee(Number(e.target.value))} - /> -
-
-
+
+ ) : null}