Show OO amplitude, Call/Put size, and min leverage in control.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-07 17:43:42 +08:00
parent bd7640e36a
commit 6d63c52ae0
2 changed files with 160 additions and 28 deletions
+21
View File
@@ -300,9 +300,15 @@ async def fleet_status(_tok: Annotated[str, Depends(require_fleet_token)]) -> di
"perp_margin_mode": st.get("perp_margin_mode"),
"perp_qty_eth": st.get("perp_qty_eth"),
"option_qty_eth": st.get("option_qty_eth"),
"oo_put_qty_eth": (
st.get("oo_put_qty_eth")
if st.get("oo_put_qty_eth") is not None
else _sf("oo_put_qty_eth", 0.0) or None
),
"sizing_mode": st.get("sizing_mode"),
"risk_last_k": st.get("risk_last_k"),
"risk_sizing_locked": st.get("risk_sizing_locked"),
"risk_sizing_preview": st.get("risk_sizing_preview"),
"risk_loss_pct": _pick("risk_loss_pct", 1.0),
"risk_perp_unit": _pick("risk_perp_unit", 1.0),
"risk_option_unit": _pick("risk_option_unit", 2.0),
@@ -310,6 +316,21 @@ async def fleet_status(_tok: Annotated[str, Depends(require_fleet_token)]) -> di
"martingale_enabled": st.get("martingale_enabled"),
"martingale_doubles": st.get("martingale_doubles"),
"risk_effective_loss_pct": st.get("risk_effective_loss_pct"),
"oo_amplitude_pct": _pick(
"oo_amplitude_pct", float(settings.oo_amplitude_pct)
),
"oo_amplitude_hours": _pick(
"oo_amplitude_hours", float(settings.oo_amplitude_hours)
),
"oo_min_option_hours": _pick(
"oo_min_option_hours", float(settings.oo_min_option_hours)
),
"oo_min_leverage": _pick(
"oo_min_leverage", float(settings.oo_min_leverage)
),
"oo_reward_ratio": _pick(
"oo_reward_ratio", float(settings.oo_reward_ratio)
),
"hedge_mode": (
hm
if (
+139 -28
View File
@@ -175,24 +175,93 @@ function closeReasonZh(v: string): string {
type RiskLines = {
riskBased: boolean;
isOo: boolean;
sizing: string;
sizingShort: string;
lossPct: string | null;
exit: string;
openRatio: string | null;
/** 表格「风险/开仓」列: 5%/1:2/12手动为 —/名义比/时长 */
/** 表格「风险/开仓」列:永期 5%/1:2/12期期 ≤2%·12h/C:P/≥24 */
riskOrOpen: string;
riskOrOpenTitle: string;
minHours: string;
leverage: string;
leverageTitle: string;
};
function isOptionOption(strat: Record<string, unknown>): boolean {
return (
String(strat.hedge_mode || "")
.trim()
.toLowerCase() === "option_option"
);
}
function riskLines(strat: Record<string, unknown>): RiskLines {
const isOo = isOptionOption(strat);
const riskBased =
strat.sizing_mode === "risk_based" || strat.risk_based === true;
if (isOo) {
const ampN = Number(strat.oo_amplitude_pct);
const ampH = Number(strat.oo_amplitude_hours);
const minH = Number(
strat.oo_min_option_hours ?? strat.min_option_hours,
);
const amp =
Number.isFinite(ampN) && Number.isFinite(ampH)
? `${fmt(ampN, ampN % 1 === 0 ? 0 : 1)}${fmt(ampH, 0)}h`
: "≤—";
const callQ =
strat.risk_sizing_preview &&
typeof strat.risk_sizing_preview === "object" &&
(strat.risk_sizing_preview as Record<string, unknown>).call_qty_eth !=
null
? (strat.risk_sizing_preview as Record<string, unknown>).call_qty_eth
: strat.option_qty_eth;
const putQ =
strat.risk_sizing_preview &&
typeof strat.risk_sizing_preview === "object" &&
(strat.risk_sizing_preview as Record<string, unknown>).put_qty_eth != null
? (strat.risk_sizing_preview as Record<string, unknown>).put_qty_eth
: strat.oo_put_qty_eth ?? strat.option_qty_eth;
const openRatio = `C${unitLabel(callQ)}:P${unitLabel(putQ)}`;
const minHours = Number.isFinite(minH) ? `${unitLabel(minH)}` : "≥—";
const ratioN = Number(strat.oo_reward_ratio);
const target = strat.exit_target_usdt ?? strat.net_profit_target;
let exit: string;
if (target != null && Number.isFinite(Number(target))) {
exit = `目标${fmt(target, 2)}U`;
} else if (Number.isFinite(ratioN)) {
exit = `预算×${fmt(ratioN, ratioN % 1 === 0 ? 0 : 2)}`;
} else {
exit = "预算×—";
}
const ooLev = Number(strat.oo_min_leverage);
const leverage = Number.isFinite(ooLev)
? `${Math.round(ooLev)}x`
: "≥—x";
return {
riskBased,
isOo: true,
sizing: riskBased ? "以损定仓(A)" : "手动开仓(B)",
sizingShort: riskBased ? "A" : "B",
lossPct: Number.isFinite(ampN)
? `${fmt(ampN, ampN % 1 === 0 ? 0 : 1)}%`
: null,
exit,
openRatio,
riskOrOpen: `${amp}/${openRatio}/${minHours}`,
riskOrOpenTitle: "振幅上限·回看 / Call:Put 数量 / 最短剩余(h)",
minHours,
leverage,
leverageTitle: "期期单腿最低杠杆",
};
}
const exitMode = String(strat.exit_mode || "fixed_usdt");
let exit: string;
if (riskBased) {
// 必须读策略机 risk_exit_unit,禁止写死 15
exit =
strat.risk_exit_unit != null && Number.isFinite(Number(strat.risk_exit_unit))
? `基数${unitLabel(strat.risk_exit_unit)}`
@@ -224,14 +293,17 @@ function riskLines(strat: Record<string, unknown>): RiskLines {
const riskOrOpen = `${lossPct || "—"}/${openRatio}/${minHours}`;
return {
riskBased,
isOo: false,
sizing: riskBased ? "以损定仓(A)" : "手动开仓(B)",
sizingShort: riskBased ? "A" : "B",
lossPct,
exit,
openRatio,
riskOrOpen,
riskOrOpenTitle: "风险比例 / 开仓比例 / 最小剩余时长(h)",
minHours,
leverage: leveragePair(strat),
leverageTitle: "永续杠杆 / 期权最低杠杆",
};
}
@@ -316,25 +388,44 @@ function RiskParamsBox({ strat }: { strat: Record<string, unknown> }) {
<dt></dt>
<dd>{r.sizing}</dd>
</div>
{r.lossPct != null ? (
<div>
<dt></dt>
<dd>{r.lossPct}</dd>
</div>
) : null}
{r.isOo ? (
<>
<div>
<dt></dt>
<dd>{r.lossPct || "—"}</dd>
</div>
<div>
<dt>Call:Put</dt>
<dd>{r.openRatio || "—"}</dd>
</div>
<div>
<dt></dt>
<dd>{r.minHours}</dd>
</div>
</>
) : (
<>
{r.lossPct != null ? (
<div>
<dt></dt>
<dd>{r.lossPct}</dd>
</div>
) : null}
{r.openRatio ? (
<div>
<dt></dt>
<dd>{r.openRatio}</dd>
</div>
) : null}
</>
)}
<div>
<dt></dt>
<dd>{r.exit}</dd>
</div>
{r.openRatio ? (
<div>
<dt></dt>
<dd>{r.openRatio}</dd>
</div>
) : null}
<div>
<dt></dt>
<dd>{r.leverage}</dd>
<dt>{r.isOo ? "期权杠杆" : "杠杆"}</dt>
<dd title={r.leverageTitle}>{r.leverage}</dd>
</div>
</dl>
</div>
@@ -832,12 +923,14 @@ export default function MonitorPage() {
<td className="mono">{s.rounds ?? "—"}</td>
<td
className="mono"
title="风险比例 / 开仓比例 / 最小剩余时长(h)"
title={risk.riskOrOpenTitle}
>
{risk.riskOrOpen}
</td>
<td className="mono">{risk.exit}</td>
<td className="mono">{risk.leverage}</td>
<td className="mono" title={risk.leverageTitle}>
{risk.leverage}
</td>
<td
className="col-actions"
onClick={(e) => e.stopPropagation()}
@@ -996,16 +1089,20 @@ export default function MonitorPage() {
<thead>
<tr>
<th></th>
{r.lossPct != null ? <th></th> : null}
{r.lossPct != null ? (
<th>{r.isOo ? "振幅" : "风险比例"}</th>
) : null}
<th></th>
{r.openRatio ? <th></th> : null}
<th></th>
{r.openRatio ? (
<th>{r.isOo ? "Call:Put" : "开仓比例"}</th>
) : null}
<th>{r.isOo ? "期权杠杆" : "杠杆"}</th>
<th></th>
<th>/</th>
<th></th>
<th> ID</th>
<th></th>
<th>/</th>
{!r.isOo ? <th></th> : null}
<th>{r.isOo ? "数量 Call/Put" : "名义永续/期权"}</th>
<th></th>
<th></th>
<th></th>
@@ -1017,7 +1114,7 @@ export default function MonitorPage() {
{r.lossPct != null ? <td>{r.lossPct}</td> : null}
<td>{r.exit}</td>
{r.openRatio ? <td>{r.openRatio}</td> : null}
<td>{r.leverage}</td>
<td title={r.leverageTitle}>{r.leverage}</td>
<td>
{detailRunning ? "运行中" : "已停"} · {detail.phase}
</td>
@@ -1036,13 +1133,27 @@ export default function MonitorPage() {
"—",
)}
</td>
<td>{String(detail.strat.perp_margin_mode || "—")}</td>
{!r.isOo ? (
<td>
{String(detail.strat.perp_margin_mode || "—")}
</td>
) : null}
<td>
{fmt(detail.strat.perp_qty_eth, 4)} /{" "}
{fmt(detail.strat.option_qty_eth, 4)} ETH
{r.isOo
? `${fmt(detail.strat.option_qty_eth, 2)} / ${fmt(
detail.strat.oo_put_qty_eth ??
detail.strat.option_qty_eth,
2,
)} ETH`
: `${fmt(detail.strat.perp_qty_eth, 4)} / ${fmt(
detail.strat.option_qty_eth,
4,
)} ETH`}
</td>
<td>{fmt(detail.strat.exit_target_usdt, 2)} USDT</td>
<td className="mono">{fmtExPx("perp", detail.index_px)}</td>
<td className="mono">
{fmtExPx("perp", detail.index_px)}
</td>
<td className="mono">
{detail.pair
? `${detail.pair.expiry_ymd || "?"} @ ${