Add weekend open skip, expiry force-close, and Chinese trade labels.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-25 10:28:33 +08:00
parent 826362b556
commit b50c134f5b
19 changed files with 525 additions and 52 deletions
+2
View File
@@ -120,6 +120,7 @@ export type PlanState = {
window_key: string | null;
rest_left_sec: number;
rest_seconds: number;
skip_weekends?: boolean;
exit_move_pct?: number;
exit_mode: "fixed_usdt" | "premium_multiple";
net_profit_target: number;
@@ -171,6 +172,7 @@ export type StrategySettings = {
net_profit_target: number;
premium_exit_multiple: number;
rest_seconds: number;
skip_weekends: boolean;
initial_equity: number;
leverage: number;
min_option_hours: number;
+68
View File
@@ -0,0 +1,68 @@
/** 交易相关英文码 → 中文展示(库内仍存英文)。 */
const STATUS_ZH: Record<string, string> = {
open: "持仓中",
closed: "已平仓",
};
const SIDE_ZH: Record<string, string> = {
long: "多",
short: "空",
call: "看涨",
put: "看跌",
};
const LEG_ZH: Record<string, string> = {
perp: "永续",
option: "期权",
};
const ACTION_ZH: Record<string, string> = {
open: "开仓",
close: "平仓",
};
const CLOSE_REASON_ZH: Record<string, string> = {
fixed_usdt: "固定净盈利达标",
premium_multiple: "权利金倍数达标",
expiry: "到期自动全平",
emergency: "紧急全平",
manual: "手动平仓",
liquidity_retry: "流动性等待后续平仓",
unknown: "未知",
};
export function statusZh(v: string | null | undefined): string {
if (!v) return "—";
return STATUS_ZH[v] || v;
}
export function sideZh(v: string | null | undefined): string {
if (!v) return "—";
return SIDE_ZH[v] || v;
}
/** 永续方向 / 期权方向,如「多/看跌」 */
export function positionSidesZh(
perp: string | null | undefined,
option: string | null | undefined,
): string {
return `${sideZh(perp)}/${sideZh(option)}`;
}
export function fillDescZh(leg: string, action: string, side: string): string {
const l = LEG_ZH[leg] || leg;
const a = ACTION_ZH[action] || action;
const s = SIDE_ZH[side] || side;
// 期权买入开仓:「期权开多」;永续:「永续开多/开空」
if (leg === "option" && action === "open") return "期权开多";
if (leg === "option" && action === "close") return "期权平多";
if (leg === "perp" && action === "open") return s === "多" ? "永续开多" : "永续开空";
if (leg === "perp" && action === "close") return s === "多" ? "永续平多" : "永续平空";
return `${l}${a}${s}`;
}
export function closeReasonZh(v: string | null | undefined): string {
if (!v) return "—";
return CLOSE_REASON_ZH[v] || v;
}
+2 -1
View File
@@ -44,6 +44,7 @@ const PHASE_ZH: Record<string, string> = {
stopped: "已停开",
outside_window: "窗外",
liquidity_wait: "流动性等待",
weekend_skip: "周末跳过",
};
export default function PlanPage() {
@@ -123,7 +124,7 @@ export default function PlanPage() {
<div>
<h2 style={{ marginTop: 0 }}></h2>
<p style={{ color: "var(--muted)", marginTop: -8 }}>
SIM · · ·
SIM · ·
</p>
{err ? <div className="err">{err}</div> : null}
+16 -1
View File
@@ -27,6 +27,7 @@ export default function SettingsPage() {
const [netTarget, setNetTarget] = useState(15);
const [premMult, setPremMult] = useState(1);
const [rest, setRest] = useState(300);
const [skipWeekends, setSkipWeekends] = useState(true);
const [leverage, setLeverage] = useState(3);
const [minHours, setMinHours] = useState(12);
const [minOptLev, setMinOptLev] = useState(100);
@@ -43,6 +44,7 @@ export default function SettingsPage() {
setNetTarget(s.net_profit_target ?? 15);
setPremMult(s.premium_exit_multiple ?? 1);
setRest(s.rest_seconds);
setSkipWeekends(s.skip_weekends !== false);
setLeverage(s.leverage ?? 3);
setMinHours(s.min_option_hours ?? 12);
setMinOptLev(s.min_option_leverage ?? 100);
@@ -97,6 +99,7 @@ export default function SettingsPage() {
net_profit_target: netTarget,
premium_exit_multiple: premMult,
rest_seconds: rest,
skip_weekends: skipWeekends,
leverage,
min_option_hours: minHours,
min_option_leverage: minOptLev,
@@ -134,7 +137,7 @@ export default function SettingsPage() {
{tab === "strategy" ? (
<div className="card settings-card">
<p className="settings-lead">
</p>
{stratOk ? <div className="settings-ok">{stratOk}</div> : null}
{err && tab === "strategy" ? <div className="err">{err}</div> : null}
@@ -282,6 +285,18 @@ export default function SettingsPage() {
onChange={(e) => setRest(Number(e.target.value))}
/>
</div>
<div className="field">
<label htmlFor="skipWe"></label>
<select
id="skipWe"
className="mono"
value={skipWeekends ? "1" : "0"}
onChange={(e) => setSkipWeekends(e.target.value === "1")}
>
<option value="1"></option>
<option value="0"></option>
</select>
</div>
<div className="field">
<label htmlFor="fee"></label>
<input
+15 -2
View File
@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import { apiFetch } from "../api/client";
import { closeReasonZh } from "../labels";
type Summary = {
groups: number;
@@ -22,6 +23,10 @@ export default function StatsPage() {
.catch((e) => setErr(e instanceof Error ? e.message : String(e)));
}, []);
const reasonEntries = s
? Object.entries(s.close_reasons).sort((a, b) => b[1] - a[1])
: [];
return (
<div className="card">
<h2 style={{ marginTop: 0 }}></h2>
@@ -46,9 +51,17 @@ export default function StatsPage() {
{s.total_fees.toFixed(2)} / {s.total_slip.toFixed(2)}
</span>
</div>
<div className="kv">
<div className="kv" style={{ alignItems: "flex-start" }}>
<span></span>
<span className="mono">{JSON.stringify(s.close_reasons)}</span>
<span className="mono" style={{ textAlign: "right" }}>
{reasonEntries.length === 0
? "—"
: reasonEntries.map(([k, n]) => (
<div key={k}>
{closeReasonZh(k)} × {n}
</div>
))}
</span>
</div>
<h3></h3>
{s.equity_curve.map((x) => (
+13 -4
View File
@@ -1,5 +1,11 @@
import { useEffect, useState } from "react";
import { apiFetch } from "../api/client";
import {
closeReasonZh,
fillDescZh,
positionSidesZh,
statusZh,
} from "../labels";
type Group = {
group_id: string;
@@ -62,10 +68,12 @@ export default function TradesPage() {
onClick={() => openGroup(g.group_id)}
>
<span className="mono">
{g.group_id} · {g.status} · {g.perp_side}/{g.option_side}
{g.group_id} · {statusZh(g.status)} ·{" "}
{positionSidesZh(g.perp_side, g.option_side)}
</span>
<span className="mono">
PnL {Number(g.realized_pnl || 0).toFixed(2)} · {g.close_reason || ""}
{Number(g.realized_pnl || 0).toFixed(2)} ·{" "}
{closeReasonZh(g.close_reason)}
</span>
</div>
))
@@ -77,10 +85,11 @@ export default function TradesPage() {
{fills.map((f) => (
<div key={f.id} className="kv">
<span className="mono">
{f.leg} {f.action} {f.side}
{fillDescZh(f.leg, f.action, f.side)}
</span>
<span className="mono">
px {f.fill_px.toFixed(4)} · qty {f.qty_eth} · fee {f.fee.toFixed(4)}
{f.fill_px.toFixed(4)} · {f.qty_eth} · {" "}
{f.fee.toFixed(4)}
</span>
</div>
))}