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_margin_mode": st.get("perp_margin_mode"),
"perp_qty_eth": st.get("perp_qty_eth"), "perp_qty_eth": st.get("perp_qty_eth"),
"option_qty_eth": st.get("option_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"), "sizing_mode": st.get("sizing_mode"),
"risk_last_k": st.get("risk_last_k"), "risk_last_k": st.get("risk_last_k"),
"risk_sizing_locked": st.get("risk_sizing_locked"), "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_loss_pct": _pick("risk_loss_pct", 1.0),
"risk_perp_unit": _pick("risk_perp_unit", 1.0), "risk_perp_unit": _pick("risk_perp_unit", 1.0),
"risk_option_unit": _pick("risk_option_unit", 2.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_enabled": st.get("martingale_enabled"),
"martingale_doubles": st.get("martingale_doubles"), "martingale_doubles": st.get("martingale_doubles"),
"risk_effective_loss_pct": st.get("risk_effective_loss_pct"), "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": ( "hedge_mode": (
hm hm
if ( if (
+139 -28
View File
@@ -175,24 +175,93 @@ function closeReasonZh(v: string): string {
type RiskLines = { type RiskLines = {
riskBased: boolean; riskBased: boolean;
isOo: boolean;
sizing: string; sizing: string;
sizingShort: string; sizingShort: string;
lossPct: string | null; lossPct: string | null;
exit: string; exit: string;
openRatio: string | null; openRatio: string | null;
/** 表格「风险/开仓」列: 5%/1:2/12手动为 —/名义比/时长 */ /** 表格「风险/开仓」列:永期 5%/1:2/12期期 ≤2%·12h/C:P/≥24 */
riskOrOpen: string; riskOrOpen: string;
riskOrOpenTitle: string;
minHours: string; minHours: string;
leverage: 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 { function riskLines(strat: Record<string, unknown>): RiskLines {
const isOo = isOptionOption(strat);
const riskBased = const riskBased =
strat.sizing_mode === "risk_based" || strat.risk_based === true; 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"); const exitMode = String(strat.exit_mode || "fixed_usdt");
let exit: string; let exit: string;
if (riskBased) { if (riskBased) {
// 必须读策略机 risk_exit_unit,禁止写死 15
exit = exit =
strat.risk_exit_unit != null && Number.isFinite(Number(strat.risk_exit_unit)) strat.risk_exit_unit != null && Number.isFinite(Number(strat.risk_exit_unit))
? `基数${unitLabel(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}`; const riskOrOpen = `${lossPct || "—"}/${openRatio}/${minHours}`;
return { return {
riskBased, riskBased,
isOo: false,
sizing: riskBased ? "以损定仓(A)" : "手动开仓(B)", sizing: riskBased ? "以损定仓(A)" : "手动开仓(B)",
sizingShort: riskBased ? "A" : "B", sizingShort: riskBased ? "A" : "B",
lossPct, lossPct,
exit, exit,
openRatio, openRatio,
riskOrOpen, riskOrOpen,
riskOrOpenTitle: "风险比例 / 开仓比例 / 最小剩余时长(h)",
minHours, minHours,
leverage: leveragePair(strat), leverage: leveragePair(strat),
leverageTitle: "永续杠杆 / 期权最低杠杆",
}; };
} }
@@ -316,25 +388,44 @@ function RiskParamsBox({ strat }: { strat: Record<string, unknown> }) {
<dt></dt> <dt></dt>
<dd>{r.sizing}</dd> <dd>{r.sizing}</dd>
</div> </div>
{r.lossPct != null ? ( {r.isOo ? (
<div> <>
<dt></dt> <div>
<dd>{r.lossPct}</dd> <dt></dt>
</div> <dd>{r.lossPct || "—"}</dd>
) : null} </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> <div>
<dt></dt> <dt></dt>
<dd>{r.exit}</dd> <dd>{r.exit}</dd>
</div> </div>
{r.openRatio ? (
<div>
<dt></dt>
<dd>{r.openRatio}</dd>
</div>
) : null}
<div> <div>
<dt></dt> <dt>{r.isOo ? "期权杠杆" : "杠杆"}</dt>
<dd>{r.leverage}</dd> <dd title={r.leverageTitle}>{r.leverage}</dd>
</div> </div>
</dl> </dl>
</div> </div>
@@ -832,12 +923,14 @@ export default function MonitorPage() {
<td className="mono">{s.rounds ?? "—"}</td> <td className="mono">{s.rounds ?? "—"}</td>
<td <td
className="mono" className="mono"
title="风险比例 / 开仓比例 / 最小剩余时长(h)" title={risk.riskOrOpenTitle}
> >
{risk.riskOrOpen} {risk.riskOrOpen}
</td> </td>
<td className="mono">{risk.exit}</td> <td className="mono">{risk.exit}</td>
<td className="mono">{risk.leverage}</td> <td className="mono" title={risk.leverageTitle}>
{risk.leverage}
</td>
<td <td
className="col-actions" className="col-actions"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
@@ -996,16 +1089,20 @@ export default function MonitorPage() {
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
{r.lossPct != null ? <th></th> : null} {r.lossPct != null ? (
<th>{r.isOo ? "振幅" : "风险比例"}</th>
) : null}
<th></th> <th></th>
{r.openRatio ? <th></th> : null} {r.openRatio ? (
<th></th> <th>{r.isOo ? "Call:Put" : "开仓比例"}</th>
) : null}
<th>{r.isOo ? "期权杠杆" : "杠杆"}</th>
<th></th> <th></th>
<th>/</th> <th>/</th>
<th></th> <th></th>
<th> ID</th> <th> ID</th>
<th></th> {!r.isOo ? <th></th> : null}
<th>/</th> <th>{r.isOo ? "数量 Call/Put" : "名义永续/期权"}</th>
<th></th> <th></th>
<th></th> <th></th>
<th></th> <th></th>
@@ -1017,7 +1114,7 @@ export default function MonitorPage() {
{r.lossPct != null ? <td>{r.lossPct}</td> : null} {r.lossPct != null ? <td>{r.lossPct}</td> : null}
<td>{r.exit}</td> <td>{r.exit}</td>
{r.openRatio ? <td>{r.openRatio}</td> : null} {r.openRatio ? <td>{r.openRatio}</td> : null}
<td>{r.leverage}</td> <td title={r.leverageTitle}>{r.leverage}</td>
<td> <td>
{detailRunning ? "运行中" : "已停"} · {detail.phase} {detailRunning ? "运行中" : "已停"} · {detail.phase}
</td> </td>
@@ -1036,13 +1133,27 @@ export default function MonitorPage() {
"—", "—",
)} )}
</td> </td>
<td>{String(detail.strat.perp_margin_mode || "—")}</td> {!r.isOo ? (
<td>
{String(detail.strat.perp_margin_mode || "—")}
</td>
) : null}
<td> <td>
{fmt(detail.strat.perp_qty_eth, 4)} /{" "} {r.isOo
{fmt(detail.strat.option_qty_eth, 4)} ETH ? `${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>
<td>{fmt(detail.strat.exit_target_usdt, 2)} USDT</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"> <td className="mono">
{detail.pair {detail.pair
? `${detail.pair.expiry_ymd || "?"} @ ${ ? `${detail.pair.expiry_ymd || "?"} @ ${