Show OO amplitude, Call/Put size, and min leverage in control.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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 (
|
||||
|
||||
@@ -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 || "?"} @ ${
|
||||
|
||||
Reference in New Issue
Block a user