import { useEffect, useRef, useState } from "react";
import { apiFetch, MarketSnapshot, PlanState } from "../api/client";
import { fmtBidLiqEx, fmtExPx, fmtTopEx } from "../format";

function fmt(n: number | null | undefined, d = 2) {
  if (n == null || Number.isNaN(n)) return "—";
  return n.toFixed(d);
}

/** 期权杠杆 = 标的 ÷ 权利金 */
function optionLevLabel(
  underlying: number | null | undefined,
  premium: number | null | undefined,
) {
  if (
    underlying == null ||
    premium == null ||
    !Number.isFinite(underlying) ||
    !Number.isFinite(premium) ||
    underlying <= 0 ||
    premium <= 0
  ) {
    return "—";
  }
  return `${(underlying / premium).toFixed(0)}x`;
}

function pnlClass(n: number | null | undefined) {
  if (n == null || Number.isNaN(n) || n === 0) return "";
  return n > 0 ? "pos-pnl-profit" : "pos-pnl-loss";
}

/** OKX 期权到期：YYMMDD 当日 08:00 UTC（上海 16:00） */
function expiryMsFromYmd(ymd: string | null | undefined): number | null {
  if (!ymd || ymd.length !== 6) return null;
  const yy = 2000 + Number(ymd.slice(0, 2));
  const mm = Number(ymd.slice(2, 4));
  const dd = Number(ymd.slice(4, 6));
  if (![yy, mm, dd].every((x) => Number.isFinite(x))) return null;
  return Date.UTC(yy, mm - 1, dd, 8, 0, 0);
}

function formatCountdown(msLeft: number): string {
  if (msLeft <= 0) return "已到期";
  const s = Math.floor(msLeft / 1000);
  const d = Math.floor(s / 86400);
  const h = Math.floor((s % 86400) / 3600);
  const m = Math.floor((s % 3600) / 60);
  const sec = s % 60;
  if (d > 0) return `${d}天 ${h}时 ${String(m).padStart(2, "0")}分`;
  if (h > 0) return `${h}时 ${String(m).padStart(2, "0")}分 ${String(sec).padStart(2, "0")}秒`;
  return `${m}分 ${String(sec).padStart(2, "0")}秒`;
}

/** 上海时区开仓时间 */
function fmtOpenTime(ms: number | null | undefined) {
  if (ms == null || !Number.isFinite(ms) || ms <= 0) return "—";
  return new Date(ms).toLocaleString("zh-CN", {
    timeZone: "Asia/Shanghai",
    hour12: false,
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
  });
}

/** 持仓时长（实时） */
function fmtHoldDuration(openAtMs: number | null | undefined, now: number) {
  if (openAtMs == null || !Number.isFinite(openAtMs) || openAtMs <= 0) return "—";
  const ms = Math.max(0, now - openAtMs);
  const totalSec = Math.floor(ms / 1000);
  const h = Math.floor(totalSec / 3600);
  const m = Math.floor((totalSec % 3600) / 60);
  const s = totalSec % 60;
  if (h > 0) return `${h}时${m}分${s}秒`;
  if (m > 0) return `${m}分${s}秒`;
  return `${s}秒`;
}

const PHASE_ZH: Record<string, string> = {
  idle: "空闲",
  wait_signal: "等待信号",
  wait_human: "等待人工授权",
  opening: "开仓中",
  open: "持仓中",
  closing: "平仓中",
  resting: "休息中",
  paused: "已暂停",
  stopped: "已停开",
  outside_window: "窗外",
  liquidity_wait: "流动性等待",
  weekend_skip: "周末跳过",
  wait_funds: "资金不足",
};

function fmtBidLiquidity(
  bidPx: number | null | undefined,
  bidSzEth: number | null | undefined,
): string {
  if (bidPx == null && bidSzEth == null) return "—";
  const px = bidPx == null ? "—" : fmtExPx("option", bidPx);
  const sz = bidSzEth == null ? "—" : fmt(bidSzEth, 2);
  return `${px} / ${sz}`;
}

type LadderRow = {
  strike: number;
  offset: number;
  tag: string;
  ask: number | null;
  ask_sz: number | null;
  lev: number | null;
  inst_id?: string | null;
};

/** 与报价表相同的候选档：ATM 1 / OTM 最近 2 / ITM 最近 2 */
function filterSemiLadderRows(
  all: LadderRow[],
  moneyness: "itm" | "atm" | "otm",
  otmOff: number,
  view: "long" | "short",
): LadderRow[] {
  if (moneyness === "atm") {
    return all.filter((r) => r.tag === "atm").slice(0, 1);
  }
  if (moneyness === "otm") {
    const shown = all
      .filter(
        (r) => r.tag === "otm" && Math.abs(r.offset) <= Number(otmOff) + 1e-9,
      )
      .sort((a, b) => Math.abs(a.offset) - Math.abs(b.offset))
      .slice(0, 2);
    shown.sort((a, b) =>
      view === "short" ? a.strike - b.strike : b.strike - a.strike,
    );
    return shown;
  }
  const shown = all
    .filter((r) => r.tag === "itm")
    .sort((a, b) => Math.abs(a.offset) - Math.abs(b.offset))
    .slice(0, 2);
  shown.sort((a, b) =>
    view === "short" ? a.strike - b.strike : b.strike - a.strike,
  );
  return shown;
}

/** 定仓用：取最接近现价且有卖一的一档 */
function pickSizingAskRow(rows: LadderRow[]): LadderRow | null {
  const withAsk = rows.filter(
    (r) => r.ask != null && Number.isFinite(r.ask) && Number(r.ask) > 0,
  );
  if (withAsk.length === 0) return null;
  return [...withAsk].sort(
    (a, b) => Math.abs(a.offset) - Math.abs(b.offset),
  )[0];
}

function floorK1dp(kRaw: number): number {
  if (!(kRaw > 0) || !Number.isFinite(kRaw)) return 0;
  return Math.floor(kRaw * 10 + 1e-12) / 10;
}

/** 卖一推期权量，再按永续:期权单位配比得永续量（与后端 compute_k 一致） */
function estimateSemiRiskQty(args: {
  budget: number;
  indexPx: number;
  optionAsk: number;
  feeRate: number;
  perpUnit: number;
  optionUnit: number;
  exitUnit: number;
}): {
  ok: boolean;
  detail: string;
  k?: number;
  perpQty?: number;
  optionQty?: number;
  maxLoss?: number;
  netTarget?: number;
} {
  const {
    budget,
    indexPx,
    optionAsk,
    feeRate,
    perpUnit,
    optionUnit,
    exitUnit,
  } = args;
  if (!(budget > 0) || !(indexPx > 0) || !(optionAsk > 0)) {
    return { ok: false, detail: "缺预算/指数/卖一" };
  }
  if (!(perpUnit > 0) || !(optionUnit > 0) || !(exitUnit > 0)) {
    return { ok: false, detail: "配比/净利基数须 > 0" };
  }
  const cost1 = optionAsk * optionUnit + indexPx * feeRate * 3;
  if (!(cost1 > 1e-12)) return { ok: false, detail: "单位成本无效" };
  let k = floorK1dp(budget / cost1);
  if (k < 0.1 - 1e-12) {
    return {
      ok: false,
      detail: `预算不足以开最小仓（单位成本≈${cost1.toFixed(2)}U）`,
    };
  }
  while (k >= 0.1 - 1e-12) {
    const prem = optionAsk * optionUnit * k;
    const fee = indexPx * feeRate * 3 * k;
    const mx = prem + fee;
    if (mx <= budget + 1e-6) {
      return {
        ok: true,
        detail: "ok",
        k,
        perpQty: Number((perpUnit * k).toFixed(4)),
        optionQty: Number((optionUnit * k).toFixed(4)),
        maxLoss: Number(mx.toFixed(2)),
        netTarget: Number((exitUnit * k).toFixed(2)),
      };
    }
    k = Math.round((k - 0.1) * 10) / 10;
  }
  return { ok: false, detail: "预算内无合规 k" };
}

/**
 * 指数走到「行权价 ± 波动点」时的组合盈亏粗估。
 * 多=Call 目标 K+N；空=Put 目标 K−N。不含 IV/时间价值。
 */
function estimateSemiMoveProfit(args: {
  view: "long" | "short";
  indexPx: number;
  strike: number;
  movePoints: number;
  optionQty: number;
  perpQty: number;
  feeRate?: number;
}): {
  ok: boolean;
  detail: string;
  targetPx?: number;
  optionPnl?: number;
  perpPnl?: number;
  feesEst?: number;
  netEst?: number;
} {
  const {
    view,
    indexPx,
    strike,
    movePoints,
    optionQty,
    perpQty,
    feeRate = 0,
  } = args;
  const S = Number(indexPx);
  const K = Number(strike);
  const move = Number(movePoints);
  const oq = Number(optionQty);
  const pq = Number(perpQty);
  if (!(S > 0) || !(K > 0) || !(move > 0) || !(oq > 0) || !(pq > 0)) {
    return { ok: false, detail: "缺指数/行权价/数量" };
  }
  const tgt = view === "long" ? K + move : K - move;
  const dSpot = tgt - S;
  const callIntr = (px: number) => Math.max(0, px - K);
  const putIntr = (px: number) => Math.max(0, K - px);
  const dIntr =
    view === "long"
      ? callIntr(tgt) - callIntr(S)
      : putIntr(tgt) - putIntr(S);
  const optionPnl = dIntr * oq;
  // 多：空永续；空：多永续 → 指数变化 dSpot 时永续盈亏 = −dSpot×qty
  const perpPnl = -dSpot * pq;
  const fees =
    feeRate > 0 ? S * feeRate * 3 * Math.max(oq, pq) /* 粗估开平 */ : 0;
  const net = optionPnl + perpPnl - fees;
  return {
    ok: true,
    detail: "ok",
    targetPx: tgt,
    optionPnl: Number(optionPnl.toFixed(2)),
    perpPnl: Number(perpPnl.toFixed(2)),
    feesEst: Number(fees.toFixed(2)),
    netEst: Number(net.toFixed(2)),
  };
}

export default function PlanPage() {
  const [snap, setSnap] = useState<MarketSnapshot | null>(null);
  const [plan, setPlan] = useState<PlanState | null>(null);
  const [err, setErr] = useState("");
  const [busy, setBusy] = useState("");
  const [residualBusy, setResidualBusy] = useState("");
  const [nowMs, setNowMs] = useState(() => Date.now());
  const [semiView, setSemiView] = useState<"long" | "short">("long");
  const [semiMove, setSemiMove] = useState(50);
  const [semiExitU, setSemiExitU] = useState(5);
  const [semiMinH, setSemiMinH] = useState(30);
  const [semiMinLev, setSemiMinLev] = useState(200);
  const [semiMny, setSemiMny] = useState<"itm" | "atm" | "otm">("otm");
  const [semiOtmOff, setSemiOtmOff] = useState(25);
  const [semiPerpU, setSemiPerpU] = useState(1);
  const [semiOptU, setSemiOptU] = useState(4);
  const [semiDirty, setSemiDirty] = useState(false);
  const semiDirtyRef = useRef(false);
  const semiViewRef = useRef<"long" | "short">("long");
  const [planTab, setPlanTab] = useState<"semi" | "monitor">("semi");
  const [ladder, setLadder] = useState<{
    ok?: boolean;
    detail?: string;
    side?: string;
    index_px?: number | null;
    expiry_ymd?: string;
    hours_left?: number | null;
    min_hours?: number;
    atm_strike?: number;
    rows?: Array<{
      strike: number;
      offset: number;
      tag: string;
      ask: number | null;
      ask_sz: number | null;
      lev: number | null;
    }>;
  } | null>(null);
  const semiMinHRef = useRef(30);

  const semiParamsBody = () => ({
    semi_view_side: semiView,
    semi_option_move_points: semiMove,
    semi_perp_exit_unit: semiExitU,
    semi_min_option_hours: semiMinH,
    semi_min_option_leverage: semiMinLev,
    semi_moneyness: semiMny,
    semi_otm_max_offset: semiOtmOff,
    semi_perp_unit: semiPerpU,
    semi_option_unit: semiOptU,
  });

  async function refresh() {
    try {
      const [m, p] = await Promise.all([
        apiFetch<MarketSnapshot>("/api/market/snapshot"),
        apiFetch<PlanState>("/api/plan/state"),
      ]);
      setSnap(m);
      setPlan(p);
      // 用 ref：interval 闭包里的 semiDirty 会过期，导致改行权类型被刷回虚值
      if (!semiDirtyRef.current) {
        setSemiView(p.semi_view_side === "short" ? "short" : "long");
        setSemiMove(Number(p.semi_option_move_points ?? 50));
        setSemiExitU(Number(p.semi_perp_exit_unit ?? 5));
        setSemiMinH(Number(p.semi_min_option_hours ?? 30));
        setSemiMinLev(Number(p.semi_min_option_leverage ?? 200));
        const mny = p.semi_moneyness;
        setSemiMny(mny === "itm" || mny === "atm" || mny === "otm" ? mny : "otm");
        setSemiOtmOff(Number(p.semi_otm_max_offset ?? 25));
        setSemiPerpU(Number(p.semi_perp_unit ?? 1));
        setSemiOptU(Number(p.semi_option_unit ?? 4));
      }
      if (
        p.semi_auto_enabled &&
        String(p.hedge_mode || "") !== "option_option"
      ) {
        try {
          const viewSide = semiDirtyRef.current
            ? semiViewRef.current
            : p.semi_view_side === "short"
              ? "short"
              : "long";
          const optSide = viewSide === "short" ? "put" : "call";
          const minH = semiMinHRef.current;
          setLadder(
            await apiFetch<NonNullable<typeof ladder>>(
              `/api/market/option-ladder?wings=5&side=${optSide}&min_hours=${minH}`,
            ),
          );
        } catch {
          /* ignore ladder errors */
        }
      }
      setErr("");
    } catch (e) {
      setErr(e instanceof Error ? e.message : String(e));
    }
  }

  async function saveSemiParams() {
    setBusy("semi-save");
    setErr("");
    try {
      const p = await apiFetch<PlanState>("/api/plan/semi/params", {
        method: "PUT",
        body: JSON.stringify(semiParamsBody()),
      });
      setPlan(p);
      setSemiDirty(false);
    } catch (e) {
      setErr(e instanceof Error ? e.message : String(e));
    } finally {
      setBusy("");
    }
  }

  async function armSemi(armed: boolean) {
    setBusy(armed ? "semi-arm" : "semi-disarm");
    setErr("");
    try {
      if (semiDirty) {
        await apiFetch<PlanState>("/api/plan/semi/params", {
          method: "PUT",
          body: JSON.stringify(semiParamsBody()),
        });
        setSemiDirty(false);
      }
      const p = await apiFetch<PlanState>("/api/plan/semi/arm", {
        method: "POST",
        body: JSON.stringify({ armed }),
      });
      setPlan(p);
    } catch (e) {
      setErr(e instanceof Error ? e.message : String(e));
    } finally {
      setBusy("");
    }
  }

  useEffect(() => {
    semiDirtyRef.current = semiDirty;
  }, [semiDirty]);

  useEffect(() => {
    semiViewRef.current = semiView;
  }, [semiView]);

  useEffect(() => {
    semiMinHRef.current = Number(semiMinH) > 0 ? Number(semiMinH) : 30;
  }, [semiMinH]);

  useEffect(() => {
    refresh();
    const t = window.setInterval(refresh, 1500);
    return () => window.clearInterval(t);
  }, []);

  useEffect(() => {
    const t = window.setInterval(() => setNowMs(Date.now()), 1000);
    return () => window.clearInterval(t);
  }, []);

  async function act(path: string, label: string) {
    setBusy(label);
    setErr("");
    try {
      await apiFetch(path, { method: "POST", body: "{}" });
      await refresh();
    } catch (e) {
      setErr(e instanceof Error ? e.message : String(e));
    } finally {
      setBusy("");
    }
  }

  async function closeResidual(groupId: string, optionInstId: string) {
    const ok = window.confirm(
      `确认平掉残留期权？\n\n组：${groupId}\n合约：${optionInstId}\n\n仅校验买一流动性，不要求权利金回收比例。`,
    );
    if (!ok) return;
    setResidualBusy(groupId);
    setErr("");
    try {
      await apiFetch("/api/plan/residual/close", {
        method: "POST",
        body: JSON.stringify({ group_id: groupId }),
      });
      await refresh();
    } catch (e) {
      setErr(e instanceof Error ? e.message : String(e));
    } finally {
      setResidualBusy("");
    }
  }

  const bias = snap?.ask_compare?.bias;
  const pos = plan?.position;
  const open = !!pos?.has_position;
  // 持仓中展示本组成交方向，勿用监控 ATM 的实时盘口信号（会漂）
  const heldOpt = String(pos?.option_side || "").toLowerCase();
  const heldPerp = String(pos?.perp_side || "").toLowerCase();

  const isOo =
    plan?.hedge_mode === "option_option" ||
    snap?.hedge_mode === "option_option" ||
    pos?.hedge_mode === "option_option" ||
    !!pos?.option2_inst_id;
  const semiOn = !!plan?.semi_auto_enabled && !isOo;
  // 半自动：信号=人工看法，勿展示 ATM 盘口比价（会显示成 Put 造成误会）
  const biasTag = open ? (
    heldOpt === "call" || heldPerp === "short" ? (
      <span className="tag up">买 Call + 永续空</span>
    ) : heldOpt === "put" || heldPerp === "long" ? (
      <span className="tag down">买 Put + 永续多</span>
    ) : (
      <span className="tag">持仓中</span>
    )
  ) : semiOn ? (
    (semiDirty ? semiView : plan?.semi_view_side === "short" ? "short" : "long") ===
    "short" ? (
      <span className="tag down">半自动空 · Put + 永续多</span>
    ) : (
      <span className="tag up">半自动多 · Call + 永续空</span>
    )
  ) : bias === "strike_below_spot" ||
    bias === "call_ask_gt_put" ||
    bias === "fixed_short_call" ? (
    <span className="tag up">买 Call + 永续空</span>
  ) : bias === "strike_above_spot" ||
    bias === "put_ask_gt_call" ||
    bias === "fixed_long_put" ? (
    <span className="tag down">买 Put + 永续多</span>
  ) : (
    <span className="tag">等待 / 相等</span>
  );
  const showAmpCard = plan?.oo_amplitude_filter_enabled === true;

  // 看法 / 最短小时变化时立刻刷新报价链（小时输入防抖）
  useEffect(() => {
    if (!semiOn || planTab !== "semi") return;
    const optSide = semiView === "short" ? "put" : "call";
    const minH = Number(semiMinH) > 0 ? Number(semiMinH) : 30;
    let cancelled = false;
    const t = window.setTimeout(() => {
      void (async () => {
        try {
          const lad = await apiFetch<NonNullable<typeof ladder>>(
            `/api/market/option-ladder?wings=5&side=${optSide}&min_hours=${minH}`,
          );
          if (!cancelled) setLadder(lad);
        } catch {
          /* ignore */
        }
      })();
    }, 350);
    return () => {
      cancelled = true;
      window.clearTimeout(t);
    };
  }, [semiView, semiMinH, semiOn, planTab]);
  const exitMode = plan?.exit_mode ?? "fixed_usdt";
  const riskBased =
    plan?.risk_based === true || plan?.sizing_mode === "risk_based";
  const riskLocked =
    open &&
    (plan?.risk_sizing_locked === true ||
      plan?.risk_sizing_preview?.locked === true);
  const riskLiveOk =
    !riskBased || riskLocked || plan?.risk_sizing_preview?.ok === true;
  const displayPerpQty = riskLiveOk ? (plan?.perp_qty_eth ?? 1) : null;
  const displayOptQty = riskLiveOk ? (plan?.option_qty_eth ?? 2) : null;
  const exitTarget = riskLiveOk
    ? (plan?.exit_target_usdt ?? plan?.net_profit_target ?? 15)
    : null;
  const netPnl = pos?.net_pnl ?? 0;
  const movePct = pos?.move_pct ?? 0;
  const ooRatio = Number(plan?.oo_reward_ratio ?? 2);
  const ooBudget = plan?.risk_sizing_preview?.budget;
  const semiExitMove = Number(semiMove) || 50;
  const semiExitView: "long" | "short" =
    (semiDirty ? semiView : plan?.semi_view_side === "short" ? "short" : "long") ===
    "short"
      ? "short"
      : "long";
  const semiExitStrike =
    open && pos?.strike != null && Number.isFinite(Number(pos.strike))
      ? Number(pos.strike)
      : null;
  const semiOptTarget =
    semiExitStrike != null
      ? semiExitView === "short"
        ? semiExitStrike - semiExitMove
        : semiExitStrike + semiExitMove
      : null;
  const semiNetLock = Number(
    plan?.semi_net_exit_target ??
      (riskBased
        ? Number(semiExitU) *
          Number(plan?.risk_last_k ?? plan?.risk_sizing_preview?.k ?? 1)
        : semiExitU),
  );
  const exitRuleLabel = semiOn ? (
    <span className="plan-exit-semi">
      <span>
        {semiOptTarget != null
          ? `期权目标 K${Math.round(semiExitStrike!)}→${fmtExPx("index", semiOptTarget)}`
          : `期权目标 行权价${semiExitView === "short" ? "−" : "+"}${fmt(semiExitMove, 0)}`}
      </span>
      <span>{`永续目标 净利锁定≥${fmt(semiNetLock, 2)}U`}</span>
    </span>
  ) : isOo ? (
    exitTarget != null
      ? `${riskLocked ? "锁定 " : ""}净盈≥${fmt(exitTarget)} U（${
          ooBudget != null ? `${fmt(ooBudget)}×` : "预算×"
        }${fmt(ooRatio, 1)}）`
      : `净盈≥预算×${fmt(ooRatio, 1)}（达标只平盈利腿）`
  ) : exitMode === "premium_multiple" ? (
    riskBased
      ? exitTarget != null
        ? `${riskLocked ? "锁定 " : ""}净盈≥${fmt(exitTarget)} U（权利金×${fmt(plan?.premium_exit_multiple ?? 1, 2)}）`
        : `权利金×${fmt(plan?.premium_exit_multiple ?? 1, 2)}（开仓后锁定）`
      : `权利金×${fmt(plan?.premium_exit_multiple ?? 1, 2)}`
  ) : riskBased ? (
    exitTarget != null
      ? `${riskLocked ? "锁定 " : "固定 "}${fmt(exitTarget)} U（基数${fmt(plan?.risk_exit_unit ?? 15)}）`
      : `待估算（基数${fmt(plan?.risk_exit_unit ?? 15)}）`
  ) : (
    `固定 ${fmt(plan?.net_profit_target ?? 15)} U`
  );
  const riskRatioLabel = isOo
    ? "预算平分 Call/Put"
    : `比例${Number(plan?.risk_perp_unit ?? 1)}:${Number(plan?.risk_option_unit ?? 2)}`;
  const sizingModeLabel = riskBased
    ? [
        isOo ? "期期对冲" : "以损定仓",
        riskRatioLabel,
        !isOo && plan?.risk_last_k != null
          ? `k=${fmt(plan.risk_last_k, 1)}`
          : null,
        riskLocked
          ? exitTarget != null
            ? `目标锁定${fmt(exitTarget, 2)}U`
            : "目标已锁定"
          : plan?.risk_sizing_preview?.budget != null
            ? `预算${fmt(plan.risk_sizing_preview.budget, 2)}U`
            : null,
        plan?.martingale_enabled &&
        (plan?.martingale_doubles ?? 0) > 0 &&
        !riskLocked
          ? `倍投×${2 ** Number(plan.martingale_doubles)}(${fmt(plan.risk_effective_loss_pct ?? plan.risk_loss_pct ?? 0, 2)}%)`
          : plan?.martingale_enabled && !riskLocked
            ? "倍投开"
            : null,
        !riskLocked && plan?.risk_sizing_preview?.ok === false
          ? String(plan.risk_sizing_preview.detail || "预览失败")
          : null,
      ]
        .filter(Boolean)
        .join("  ")
    : isOo
      ? "期期对冲"
      : "手动仓位";
  const phaseLabel = PHASE_ZH[plan?.phase || ""] || plan?.phase || "—";
  const atmRule = plan?.fixed_direction_enabled
    ? plan?.fixed_perp_side === "short"
      ? "固定：永续空+买Call(实/平)"
      : "固定：永续多+买Put(实/平)"
    : plan?.atm_open_offset_enabled
      ? `ATM偏差≤${fmt(plan?.max_atm_open_offset ?? 3, 0)}`
      : "ATM偏差关";
  const ooSelectLabel = [
    plan?.oo_amplitude_filter_enabled
      ? `振幅≤${fmt(plan?.oo_amplitude_pct ?? 1.5, 1)}%/${fmt(plan?.oo_amplitude_hours ?? 12, 0)}h`
      : `振幅回看${fmt(plan?.oo_amplitude_hours ?? 12, 0)}h(过滤关)`,
    `贴高低≤${fmt(plan?.oo_strike_max_dev_pct ?? 1, 1)}%`,
    "虚值Call@高·Put@低",
    `剩余≥${fmt(plan?.oo_min_option_hours ?? 24, 0)}h`,
    `杠杆≥${fmt(plan?.oo_min_leverage ?? 200, 0)}x`,
    displayOptQty != null
      ? `Call${fmt(displayOptQty, 1)}/Put${fmt(
          Number(
            plan?.risk_sizing_preview?.put_qty_eth ??
              plan?.oo_put_qty_eth ??
              displayOptQty,
          ),
          1,
        )}ETH`
      : null,
  ]
    .filter(Boolean)
    .join(" · ");
  const poAmpLabel = plan?.oo_amplitude_filter_enabled
    ? `振幅≤${fmt(plan?.oo_amplitude_pct ?? 1.5, 1)}%/${fmt(plan?.oo_amplitude_hours ?? 12, 0)}h(第一关)`
    : null;
  const exitDetail = isOo
    ? `净盈利≥预算×${fmt(ooRatio, 1)} → 只平盈利腿，亏损腿残留`
    : exitMode === "premium_multiple"
      ? `净盈利≥初始权利金×${fmt(plan?.premium_exit_multiple ?? 1, 2)}`
      : `净盈利≥${fmt(plan?.net_profit_target ?? 15)} U`;
  const expiryMs =
    pos?.expiry_ms ?? expiryMsFromYmd(pos?.expiry_ymd) ?? null;
  const countdown =
    open && expiryMs != null ? formatCountdown(expiryMs - nowMs) : "—";
  const countdownUrgent =
    open && expiryMs != null && expiryMs - nowMs > 0 && expiryMs - nowMs < 6 * 3600_000;

  const venueShort =
    plan?.mode === "LIVE"
      ? `实盘·${(snap?.exchange || "okx").toUpperCase()}`
      : "模拟盘";
  const openAtMs = pos?.open_at_ms ?? null;
  const openTimeLabel = fmtOpenTime(openAtMs);
  const holdDurationLabel = open ? fmtHoldDuration(openAtMs, nowMs) : "—";

  return (
    <div className="plan-page">
      {/* 手机端一屏监控卡：状态 / 净利 / 倒计时 */}
      <section className="plan-mobile-hero" aria-label="监控摘要">
        <div className="plan-mobile-hero-top">
          <span
            className={
              plan?.running
                ? "plan-pill plan-pill-on"
                : plan?.phase === "paused"
                  ? "plan-pill plan-pill-pause"
                  : "plan-pill plan-pill-off"
            }
          >
            {plan?.running ? "启动中" : plan?.phase === "paused" ? "已暂停" : "已停"}
          </span>
          <span className="plan-pill plan-pill-muted mono">{venueShort}</span>
          <span className="plan-mobile-phase mono">{phaseLabel}</span>
        </div>
        <div className="plan-mobile-hero-pnl">
          <div className="plan-mobile-pnl-block">
            <span className="plan-mobile-label">净盈利</span>
            <span className={`plan-mobile-pnl-num mono ${pnlClass(open ? netPnl : null)}`}>
              {open ? fmt(netPnl) : "—"}
            </span>
            <span className="plan-mobile-sub mono">
              目标 {exitTarget != null ? `${fmt(exitTarget)} U` : "—"}
            </span>
          </div>
          <div className="plan-mobile-pnl-block">
            <span className="plan-mobile-label">到期</span>
            <span
              className={`plan-mobile-countdown mono ${countdownUrgent ? "pos-countdown-urgent" : ""}`}
            >
              {countdown}
            </span>
            <span className="plan-mobile-sub mono">
              {open ? pos?.group_id || "持仓中" : "空仓"}
            </span>
          </div>
        </div>
        {plan?.last_error ? (
          <div className="plan-mobile-err">{plan.last_error}</div>
        ) : null}
      </section>

      {err ? <div className="err">{err}</div> : null}

      <div className="plan-rules-fold">
        <details>
          <summary>规则说明</summary>
          <div className="plan-rules-body">
            <p>
              <span className="plan-rules-k">行情</span>
              {String(plan?.exchange || "okx").toUpperCase()} · ETH-USDT-SWAP
            </p>
            <p>
              <span className="plan-rules-k">模式</span>
              {sizingModeLabel}
            </p>
            <p>
              <span className="plan-rules-k">开仓</span>
              {isOo ? (
                <>
                  {ooSelectLabel}
                  {plan?.skip_weekends ? " · 周末跳过开仓" : ""}
                  {plan?.one_expiry_per_day !== false
                    ? " · 每个到期只开一次"
                    : ""}
                </>
              ) : (
                <>
                  {poAmpLabel ? `${poAmpLabel} · ` : ""}
                  永续{" "}
                  {displayPerpQty != null
                    ? `${fmt(displayPerpQty, 2)} ETH`
                    : "—"}{" "}
                  / {fmt(plan?.leverage, 0)}x
                  · 期权{" "}
                  {displayOptQty != null
                    ? `${fmt(displayOptQty, 2)} ETH`
                    : "—"}{" "}
                  名义 · 剩余≥
                  {fmt(plan?.min_option_hours, 0)}h · 期权杠杆≥
                  {fmt(plan?.min_option_leverage, 0)}x · {atmRule}
                  {plan?.skip_weekends ? " · 周末跳过开仓" : ""}
                  {plan?.one_expiry_per_day !== false
                    ? " · 每个到期只开一次"
                    : ""}
                </>
              )}
            </p>
            <p>
              <span className="plan-rules-k">平仓</span>
              {isOo
                ? `${exitDetail}；否则到期结算`
                : `${exitDetail} → 双腿全平；期权远虚则只平永续、期权到期结算；未达标拖到期`}
            </p>
            <p>
              <span className="plan-rules-k">节奏</span>
              组间休息 {plan?.rest_seconds ?? "—"}s · 可开判定看交易账户
              {plan?.one_expiry_per_day !== false
                ? " · 同到期用过后改开下一到期"
                : ""}
            </p>
          </div>
        </details>
      </div>

      <div className="plan-actions">
        <button
          className={plan?.running ? "btn btn-running" : "btn"}
          type="button"
          disabled={
            !!busy ||
            !!plan?.running ||
            (plan?.mode === "LIVE" && plan.live_ready === false)
          }
          title={
            plan?.mode === "LIVE" && plan.live_ready === false
              ? plan.live_ready_reason || "LIVE 未就绪"
              : undefined
          }
          onClick={() => act("/api/plan/start", "start")}
        >
          {plan?.running ? "启动中" : "启动策略"}
        </button>
        <button
          className="btn ghost"
          type="button"
          disabled={!!busy || !plan?.running}
          onClick={() => act("/api/plan/pause", "pause")}
        >
          暂停
        </button>
        {plan?.show_manual_trade_buttons ? (
          <>
            <button
              className="btn ghost"
              type="button"
              disabled={
                !!busy ||
                !!plan?.running ||
                (plan?.mode === "LIVE" && plan.live_ready === false)
              }
              title={
                plan?.running
                  ? "策略自动运行中，禁止手动开仓"
                  : plan?.mode === "LIVE" && plan.live_ready === false
                    ? plan.live_ready_reason || "LIVE 未就绪"
                    : undefined
              }
              onClick={() => act("/api/sim/open-group", "open")}
            >
              手动开一组
            </button>
            <button
              className="btn ghost"
              type="button"
              disabled={!!busy}
              onClick={() => act("/api/sim/close-group", "close")}
            >
              手动全平
            </button>
          </>
        ) : null}
        <button
          className="btn danger"
          type="button"
          disabled={!!busy}
          onClick={() => act("/api/plan/emergency-close", "emg")}
        >
          紧急全平
        </button>
        {busy ? <span className="meta">{busy}…</span> : null}
      </div>

      {semiOn ? (
        <div className="tabs plan-main-tabs" role="tablist" aria-label="计划页">
          <button
            type="button"
            role="tab"
            className={planTab === "semi" ? "tab active" : "tab"}
            aria-selected={planTab === "semi"}
            onClick={() => setPlanTab("semi")}
          >
            半自动单
          </button>
          <button
            type="button"
            role="tab"
            className={planTab === "monitor" ? "tab active" : "tab"}
            aria-selected={planTab === "monitor"}
            onClick={() => setPlanTab("monitor")}
          >
            持仓监控
          </button>
        </div>
      ) : null}

      {semiOn && planTab === "semi" ? (
        <div className="plan-semi-tab">
          <section className="card plan-semi-card plan-semi-compact" aria-label="半自动本单">
            <div className="plan-semi-head">
              <h3 className="plan-panel-title">半自动 · 本单</h3>
              <span className="mono meta">
                {open ? (
                  <span className="status-running">持仓中</span>
                ) : plan.semi_armed ? (
                  "已授权"
                ) : (
                  "未授权"
                )}
                {" · "}
                {semiMny === "itm" ? "实值" : semiMny === "atm" ? "平值" : "虚值"}
                {" · "}
                {fmt(semiPerpU, 0)}:{fmt(semiOptU, 0)}
              </span>
            </div>
            <details className="plan-semi-rules">
              <summary>规则说明</summary>
              <ul>
                <li>人工定方向、行权类型、永续:期权配比与出场 →「授权开下一单」后机器盯选约/开/平。</li>
                <li>平仓后停在等待授权，不自动连开；与全自动循环无关。</li>
                <li>虚值：偏离 ≤ 设定点数；杠杆门 ≥180（默认 200）。实值/平值用本单最低杠杆。</li>
                <li>波动点 N：期权目标位 = 行权价 ± N（多 Call：K+N；空 Put：K−N），不是现价±N。</li>
                <li>顺向出场：指数到期权目标位且组合净利 &gt; 0 → 双腿全平（先期权后永续）。</li>
                <li>永续锁定：组合净利 ≥ 净利基数 × k → 全平（不必等点位）。</li>
                <li>以损定仓时配比为单位再乘 k；手动仓按配比直接开。</li>
              </ul>
            </details>
            <div className="settings-fields plan-semi-fields">
              <div className="field">
                <label htmlFor="semiView">方向</label>
                <select
                  id="semiView"
                  className="mono"
                  disabled={open || !!busy || !!plan.semi_armed}
                  value={semiView}
                  onChange={(e) => {
                    setSemiView(e.target.value === "short" ? "short" : "long");
                    setSemiDirty(true);
                  }}
                >
                  <option value="long">多 · Call+永续空</option>
                  <option value="short">空 · Put+永续多</option>
                </select>
              </div>
              <div className="field">
                <label htmlFor="semiMny">行权</label>
                <select
                  id="semiMny"
                  className="mono"
                  disabled={open || !!busy || !!plan.semi_armed}
                  value={semiMny}
                  onChange={(e) => {
                    const v = e.target.value;
                    setSemiMny(
                      v === "itm" || v === "atm" || v === "otm" ? v : "otm",
                    );
                    setSemiDirty(true);
                  }}
                >
                  <option value="itm">实值</option>
                  <option value="atm">平值</option>
                  <option value="otm">虚值</option>
                </select>
              </div>
              {semiMny === "otm" ? (
                <div className="field">
                  <label htmlFor="semiOtmOff">虚值偏离</label>
                  <input
                    id="semiOtmOff"
                    className="mono"
                    type="number"
                    step="1"
                    min="1"
                    disabled={open || !!busy || !!plan.semi_armed}
                    value={semiOtmOff}
                    onChange={(e) => {
                      setSemiOtmOff(Number(e.target.value));
                      setSemiDirty(true);
                    }}
                  />
                </div>
              ) : null}
              <div className="field">
                <label htmlFor="semiPerpU">永续单位</label>
                <input
                  id="semiPerpU"
                  className="mono"
                  type="number"
                  step="0.1"
                  min="0.01"
                  disabled={open || !!busy || !!plan.semi_armed}
                  value={semiPerpU}
                  onChange={(e) => {
                    setSemiPerpU(Number(e.target.value));
                    setSemiDirty(true);
                  }}
                />
              </div>
              <div className="field">
                <label htmlFor="semiOptU">期权单位</label>
                <input
                  id="semiOptU"
                  className="mono"
                  type="number"
                  step="0.1"
                  min="0.01"
                  disabled={open || !!busy || !!plan.semi_armed}
                  value={semiOptU}
                  onChange={(e) => {
                    setSemiOptU(Number(e.target.value));
                    setSemiDirty(true);
                  }}
                />
              </div>
              <div className="field">
                <label htmlFor="semiMove">波动点(相对K)</label>
                <input
                  id="semiMove"
                  className="mono"
                  type="number"
                  step="1"
                  min="1"
                  disabled={open || !!busy || !!plan.semi_armed}
                  value={semiMove}
                  onChange={(e) => {
                    setSemiMove(Number(e.target.value));
                    setSemiDirty(true);
                  }}
                />
              </div>
              <div className="field">
                <label htmlFor="semiExitU">净利基数</label>
                <input
                  id="semiExitU"
                  className="mono"
                  type="number"
                  step="0.1"
                  min="0.1"
                  disabled={open || !!busy || !!plan.semi_armed}
                  value={semiExitU}
                  onChange={(e) => {
                    setSemiExitU(Number(e.target.value));
                    setSemiDirty(true);
                  }}
                />
              </div>
              <div className="field">
                <label htmlFor="semiMinH">最短 h</label>
                <input
                  id="semiMinH"
                  className="mono"
                  type="number"
                  step="1"
                  min="1"
                  disabled={open || !!busy || !!plan.semi_armed}
                  value={semiMinH}
                  onChange={(e) => {
                    setSemiMinH(Number(e.target.value));
                    setSemiDirty(true);
                  }}
                />
              </div>
              <div className="field">
                <label htmlFor="semiMinLev">最低杠杆</label>
                <input
                  id="semiMinLev"
                  className="mono"
                  type="number"
                  step="1"
                  min="1"
                  disabled={open || !!busy || !!plan.semi_armed}
                  value={semiMinLev}
                  onChange={(e) => {
                    setSemiMinLev(Number(e.target.value));
                    setSemiDirty(true);
                  }}
                />
              </div>
            </div>
            <div className="mono plan-semi-lines">
              {(() => {
                const shown = filterSemiLadderRows(
                  ladder?.rows || [],
                  semiMny,
                  Number(semiOtmOff) || 25,
                  semiView,
                );
                const pick = pickSizingAskRow(shown);
                const idx =
                  ladder?.index_px != null
                    ? Number(ladder.index_px)
                    : snap?.index_px != null
                      ? Number(snap.index_px)
                      : null;
                const strike =
                  open && pos?.strike != null && Number.isFinite(Number(pos.strike))
                    ? Number(pos.strike)
                    : pick?.strike != null
                      ? Number(pick.strike)
                      : ladder?.atm_strike != null
                        ? Number(ladder.atm_strike)
                        : null;
                const ask = pick?.ask != null ? Number(pick.ask) : null;
                const fee = Number(plan.fee_rate ?? 0.0005);
                const feeOk = Number.isFinite(fee) && fee >= 0 ? fee : 0.0005;
                const moveN = Number(semiMove) || 50;

                let optQty = Number(semiOptU) || 4;
                let perpQty = Number(semiPerpU) || 1;
                let kShow: number | null = riskLocked
                  ? plan.risk_last_k != null
                    ? Number(plan.risk_last_k)
                    : null
                  : null;
                let budgetShow: number | null = null;
                let sizingLine = "";

                if (!riskBased) {
                  sizingLine = `以损定仓 · 未开启（手动 期权 ${fmt(semiOptU, 2)} / 永续 ${fmt(semiPerpU, 2)} ETH）`;
                } else if (riskLocked) {
                  optQty = Number(
                    plan.option_qty_eth ?? pos?.option_qty_eth ?? optQty,
                  );
                  perpQty = Number(
                    plan.perp_qty_eth ?? pos?.perp_qty_eth ?? perpQty,
                  );
                  sizingLine = `以损定仓(锁) · 期权 ${fmt(optQty, 2)} ETH / 永续 ${fmt(perpQty, 2)} ETH${
                    kShow != null ? ` · k=${fmt(kShow, 1)}` : ""
                  }`;
                } else {
                  const budget = Number(plan.risk_sizing_preview?.budget);
                  budgetShow = Number.isFinite(budget) ? budget : null;
                  if (
                    ask != null &&
                    idx != null &&
                    budgetShow != null &&
                    budgetShow > 0
                  ) {
                    const est = estimateSemiRiskQty({
                      budget: budgetShow,
                      indexPx: idx,
                      optionAsk: ask,
                      feeRate: feeOk,
                      perpUnit: Number(semiPerpU) || 1,
                      optionUnit: Number(semiOptU) || 4,
                      exitUnit: Number(semiExitU) || 5,
                    });
                    if (est.ok && est.optionQty != null && est.perpQty != null) {
                      optQty = est.optionQty;
                      perpQty = est.perpQty;
                      kShow = est.k ?? null;
                      sizingLine = `以损定仓 · 期权 ${fmt(optQty, 2)} ETH / 永续 ${fmt(perpQty, 2)} ETH · k=${fmt(kShow, 1)} · 预算${fmt(budgetShow, 2)}U · 卖一${fmtExPx("option", ask)}${
                        pick != null ? ` · K${Math.round(pick.strike)}` : ""
                      }`;
                    } else {
                      sizingLine = `以损定仓 · ${est.detail}`;
                    }
                  } else {
                    const prev = plan.risk_sizing_preview;
                    if (prev?.ok && prev.option_qty_eth != null) {
                      optQty = Number(prev.option_qty_eth);
                      perpQty = Number(prev.perp_qty_eth ?? perpQty);
                      kShow =
                        prev.k != null ? Number(prev.k) : kShow;
                      sizingLine = `以损定仓 · 期权 ${fmt(optQty, 2)} ETH / 永续 ${fmt(perpQty, 2)} ETH${
                        kShow != null ? ` · k=${fmt(kShow, 1)}` : ""
                      }${
                        prev.budget != null
                          ? ` · 预算${fmt(Number(prev.budget), 2)}U`
                          : ""
                      } · 待本单卖一`;
                    } else {
                      sizingLine = `以损定仓 · ${
                        prev?.ok === false
                          ? String(prev.detail || "预览失败")
                          : "待卖一/预算推算开仓量"
                      }`;
                    }
                  }
                }

                const reverseTgt = Number(
                  plan.semi_net_exit_target ??
                    (riskBased
                      ? Number(semiExitU) *
                        Number(
                          kShow ??
                            plan.risk_last_k ??
                            plan.risk_sizing_preview?.k ??
                            1,
                        )
                      : semiExitU),
                );

                let line1 = "";
                if (strike == null || !Number.isFinite(strike)) {
                  line1 = `期权目标 · 行权价${semiView === "short" ? "−" : "+"}${fmt(moveN, 0)}（待选约）`;
                } else if (idx == null || !Number.isFinite(idx)) {
                  const tgt =
                    semiView === "long" ? strike + moveN : strike - moveN;
                  line1 = `期权目标 · K${Math.round(strike)}→${fmtExPx("index", tgt)}`;
                } else {
                  const prof = estimateSemiMoveProfit({
                    view: semiView,
                    indexPx: idx,
                    strike,
                    movePoints: moveN,
                    optionQty: optQty,
                    perpQty: perpQty,
                    feeRate: 0,
                  });
                  if (!prof.ok) {
                    line1 = `期权目标 · ${prof.detail}`;
                  } else {
                    line1 = `期权目标 · K${Math.round(strike)}→${fmtExPx("index", prof.targetPx!)} · 现价${fmtExPx("index", idx)} · 到点预估≈${fmt(prof.netEst, 2)}U（期权${fmt(prof.optionPnl, 1)}/永续${fmt(prof.perpPnl, 1)}）`;
                  }
                }

                const line2 = `永续目标 · 净利锁定≥${fmt(reverseTgt, 2)}U（基数${fmt(semiExitU, 1)}×k）`;

                return (
                  <>
                    <div className="plan-semi-line">{line1}</div>
                    <div className="plan-semi-line">{line2}</div>
                    <div className="plan-semi-line">{sizingLine}</div>
                  </>
                );
              })()}
            </div>
            <div className="plan-semi-btns">
              {open ? (
                <span className="plan-semi-holding status-running">持仓中</span>
              ) : (
                <>
                  <button
                    className="btn ghost"
                    type="button"
                    disabled={!!busy || !semiDirty}
                    onClick={() => void saveSemiParams()}
                  >
                    保存
                  </button>
                  <button
                    className="btn"
                    type="button"
                    disabled={!!busy || !!plan.semi_armed}
                    onClick={() => void armSemi(true)}
                  >
                    授权开下一单
                  </button>
                  <button
                    className="btn ghost"
                    type="button"
                    disabled={!!busy || !plan.semi_armed}
                    onClick={() => void armSemi(false)}
                  >
                    取消授权
                  </button>
                </>
              )}
            </div>
          </section>

          <section className="card plan-t-quote" aria-label="期权报价列表">
            <div className="plan-t-head-stack">
              <h3 className="plan-panel-title plan-t-title-line">
                {semiView === "short" ? "Put" : "Call"}报价
                {semiMny === "itm"
                  ? "实值"
                  : semiMny === "atm"
                    ? "平值"
                    : "虚值"}
              </h3>
              <div className="mono plan-t-expiry-line">
                {ladder?.expiry_ymd
                  ? `到期 ${ladder.expiry_ymd}`
                  : "到期 —"}
                {ladder?.hours_left != null
                  ? ` · 剩余≈${fmt(ladder.hours_left, 1)}h`
                  : ""}
                {` · 门槛≥${fmt(Number(semiMinH) || 30, 0)}h`}
                {ladder?.atm_strike != null
                  ? ` · ATM ${Math.round(ladder.atm_strike)}`
                  : ""}
                {ladder?.index_px != null
                  ? ` · 指数 ${fmtExPx("index", ladder.index_px)}`
                  : ""}
              </div>
            </div>
            {!ladder?.ok ? (
              <p className="meta">{ladder?.detail || "暂无报价链"}</p>
            ) : (
              <div className="plan-t-wrap">
                <table className="plan-t-table">
                  <thead>
                    <tr>
                      <th>行权价</th>
                      <th>类型</th>
                      <th>卖一 / 流动性</th>
                      <th>杠杆</th>
                    </tr>
                  </thead>
                  <tbody>
                    {(() => {
                      const shown = filterSemiLadderRows(
                        ladder.rows || [],
                        semiMny,
                        Number(semiOtmOff) || 25,
                        semiView,
                      );
                      if (shown.length === 0) {
                        return (
                          <tr>
                            <td colSpan={4} className="meta">
                              当前无符合「
                              {semiMny === "itm"
                                ? "实值"
                                : semiMny === "atm"
                                  ? "平值"
                                  : "虚值"}
                              」的报价档
                              {semiMny === "otm"
                                ? `（偏离≤${fmt(semiOtmOff, 0)}）`
                                : ""}
                            </td>
                          </tr>
                        );
                      }
                      return shown.map((r) => {
                        const tagZh =
                          r.tag === "atm"
                            ? "平值"
                            : r.tag === "itm"
                              ? "实值"
                              : "虚值";
                        return (
                          <tr
                            key={r.strike}
                            className={
                              r.tag === "atm" ? "plan-t-atm" : "plan-t-pick"
                            }
                          >
                            <td className="mono plan-t-k">
                              {Math.round(r.strike)}
                              <span className="meta">
                                {" "}
                                ({r.offset >= 0 ? "+" : ""}
                                {fmt(r.offset, 0)})
                              </span>
                            </td>
                            <td>{tagZh}</td>
                            <td className="mono">
                              {fmtTopEx("option", r.ask, r.ask_sz)}
                            </td>
                            <td className="mono">
                              {r.lev != null ? `${r.lev.toFixed(0)}x` : "—"}
                            </td>
                          </tr>
                        );
                      });
                    })()}
                  </tbody>
                </table>
              </div>
            )}
          </section>
        </div>
      ) : null}

      {!semiOn || planTab === "monitor" ? (
      <div className="plan-shell">
        <div className="plan-board">
          <div className="card plan-strategy">
            <h3 className="plan-panel-title">策略</h3>
            <div className="plan-metrics">
              <div className="kv">
                <span>状态</span>
                <span>
                  {plan?.running ? (
                    <span className="status-running">启动中</span>
                  ) : (
                    <span className="status-stopped">已停</span>
                  )}
                  <span className="mono">
                    {" "}
                    · {phaseLabel} · 已完成 {plan?.rounds_done ?? 0} 组
                  </span>
                </span>
              </div>
              <div className="kv">
                <span>模式</span>
                <span className="mono">
                  {semiOn ? (
                    <span className="status-running">
                      半自动 ·{" "}
                      {semiMny === "itm"
                        ? "实值"
                        : semiMny === "atm"
                          ? "平值"
                          : "虚值"}{" "}
                      · 配比 {fmt(semiPerpU, 0)}:{fmt(semiOptU, 0)}
                      {plan?.semi_armed ? " · 已授权" : " · 未授权"}
                    </span>
                  ) : riskBased ? (
                    <span className="status-running">{sizingModeLabel}</span>
                  ) : (
                    sizingModeLabel
                  )}
                </span>
              </div>
              <div className="kv">
                <span>选约条件</span>
                <span className="mono">
                  {semiOn ? (
                    <>
                      剩余≥{fmt(semiMinH, 0)}h · 杠杆≥{fmt(semiMinLev, 0)}x
                      {semiMny === "otm"
                        ? ` · 虚值≤${fmt(semiOtmOff, 0)}点`
                        : ""}
                      {" · "}
                      期权目标K{semiExitView === "short" ? "−" : "+"}
                      {fmt(semiMove, 0)}且净利&gt;0 / 永续锁定≥
                      {fmt(semiExitU, 1)}×k
                    </>
                  ) : isOo ? (
                    ooSelectLabel
                  ) : (
                    <>
                      {poAmpLabel ? `${poAmpLabel} · ` : ""}
                      永续
                      {displayPerpQty != null
                        ? `${fmt(displayPerpQty, 2)}ETH`
                        : "—"}
                      /期权
                      {displayOptQty != null
                        ? `${fmt(displayOptQty, 2)}ETH`
                        : "—"}{" "}
                      · 剩余≥
                      {fmt(plan?.min_option_hours, 0)}h · 期权杠杆≥
                      {fmt(plan?.min_option_leverage, 0)}x
                      {plan?.fixed_direction_enabled
                        ? plan?.fixed_perp_side === "short"
                          ? " · 固定空+Call(实/平)"
                          : " · 固定多+Put(实/平)"
                        : plan?.atm_open_offset_enabled
                          ? ` · ATM偏差≤${fmt(plan?.max_atm_open_offset ?? 3, 0)}`
                          : " · ATM偏差关"}
                    </>
                  )}
                </span>
              </div>
              <div className="kv">
                <span>休息</span>
                <span className="mono">
                  {plan?.rest_left_sec ? `${plan.rest_left_sec}s / ${plan.rest_seconds}s` : "—"}
                </span>
              </div>
              <div className="kv">
                <span>权益 / 杠杆</span>
                <span className="open-cap-row">
                  <span
                    className={
                      plan?.open_capacity?.perp_can_open === true
                        ? "open-cap-ok"
                        : plan?.open_capacity?.perp_can_open === false
                          ? "open-cap-bad"
                          : "mono"
                    }
                    title={
                      plan?.open_capacity?.perp_need_usdt != null
                        ? `需≈${plan.open_capacity.perp_need_usdt}U / 有${plan.open_capacity.perp_have_usdt ?? "—"}U`
                        : undefined
                    }
                  >
                    {plan?.open_capacity?.perp_label ||
                      `永续${fmt(plan?.leverage, 0)}x —`}
                  </span>
                  <span className="open-cap-sep">·</span>
                  <span
                    className={
                      plan?.open_capacity?.option_can_open === true
                        ? "open-cap-ok"
                        : plan?.open_capacity?.option_can_open === false
                          ? "open-cap-bad"
                          : "mono"
                    }
                    title={
                      plan?.open_capacity?.option_need_usdc != null
                        ? `需≈${plan.open_capacity.option_need_usdc}U / 有${plan.open_capacity.option_have_usdc ?? "—"}U`
                        : undefined
                    }
                  >
                    {plan?.open_capacity?.option_label || "期权 —"}
                  </span>
                </span>
              </div>
              {isOo ? (
                <div className="kv">
                  <span>开仓结构</span>
                  <span className="mono">
                    <span className="tag">买 Call + 买 Put</span>
                    <span className="meta"> · 不按永期方向信号</span>
                  </span>
                </div>
              ) : (
                <div className="kv">
                  <span>信号方向</span>
                  <span className="mono">{biasTag}</span>
                </div>
              )}
              <div className="kv">
                <span>出场规则</span>
                <span className="mono">{exitRuleLabel}</span>
              </div>
              <div className="kv">
                <span>净盈利 / 目标</span>
                <span className="mono">
                  <span className={pnlClass(open ? netPnl : null)}>
                    {open ? fmt(netPnl) : "—"}
                  </span>
                  {" / "}
                  {open || exitMode === "fixed_usdt" || isOo
                    ? exitTarget != null
                      ? fmt(exitTarget)
                      : "—"
                    : "—"}
                </span>
              </div>
              <div className="kv">
                <span>{isOo ? "Call/Put 浮盈" : "永续/期权浮盈"}</span>
                <span className="mono">
                  <span className={pnlClass(pos?.perp_upl)}>
                    {fmt(isOo ? pos?.option_upl : pos?.perp_upl)}
                  </span>
                  {" / "}
                  <span className={pnlClass(isOo ? pos?.option2_upl : pos?.option_upl)}>
                    {fmt(isOo ? pos?.option2_upl : pos?.option_upl)}
                  </span>
                </span>
              </div>
              {plan?.mode === "LIVE" && open ? (
                <>
                  <div className="kv">
                    <span>已付手续费</span>
                    <span className="mono">{fmt(pos?.fees_paid)}</span>
                  </div>
                  <div className="kv">
                    <span>资金费</span>
                    <span className={`mono ${pnlClass(pos?.funding_usdt)}`}>
                      {fmt(pos?.funding_usdt)}
                    </span>
                  </div>
                </>
              ) : null}
              {plan?.last_error ? (
                <div className="kv">
                  <span>最近错误</span>
                  <span className="err" style={{ margin: 0 }}>
                    {plan.last_error}
                  </span>
                </div>
              ) : null}
            </div>
          </div>

          <div className="plan-positions">
            {open ? (
              <>
                {isOo ? (
                  <>
                    <div className="pos-card">
                      <div className="pos-card-head">
                        <div className="pos-card-symbol">
                          <strong>{pos?.option_inst_id || "Call"}</strong>
                          <span className="pos-side-badge pos-side-long">
                            看涨 Call
                          </span>
                        </div>
                        <div
                          className={`pos-countdown mono ${countdownUrgent ? "pos-countdown-urgent" : ""}`}
                          title="到期倒计时"
                        >
                          {countdown}
                        </div>
                      </div>
                      <div className="pos-meta">
                        <span className="pos-meta-item">期期 · Call</span>
                        <span className="pos-meta-item">
                          到期 {pos?.expiry_ymd || "—"}
                        </span>
                        <span className="pos-meta-item">
                          行权{" "}
                          {pos?.strike != null
                            ? Math.round(Number(pos.strike))
                            : "—"}
                        </span>
                        <span className="pos-meta-item mono">
                          {fmt(pos?.option_qty_eth, 2)} ETH ·{" "}
                          {fmt(pos?.option_qty_contracts, 0)} 张
                        </span>
                        <span
                          className="pos-meta-item mono"
                          title="开仓指数÷开仓均价"
                        >
                          杠杆 {fmt(pos?.option_leverage, 0)}x
                        </span>
                      </div>
                      <div className="pos-grid">
                        <div className="pos-cell">
                          <span className="pos-label">开仓均价</span>
                          <span className="pos-value mono">
                            {fmtExPx("option", pos?.option_entry_px)}
                          </span>
                        </div>
                        <div className="pos-cell">
                          <span className="pos-label">买一</span>
                          <span className="pos-value mono">
                            {fmtBidLiqEx(
                              "option",
                              pos?.option_mark_px,
                              pos?.option_bid_sz,
                            )}
                          </span>
                        </div>
                        <div className="pos-cell">
                          <span className="pos-label">
                            {plan?.mode === "LIVE" ? "净盈亏" : "浮盈亏"}
                          </span>
                          <span
                            className={`pos-value mono ${pnlClass(pos?.option_upl)}`}
                          >
                            {fmt(pos?.option_upl)}
                          </span>
                        </div>
                        <div className="pos-cell">
                          <span className="pos-label">初始权利金</span>
                          <span className="pos-value mono">
                            {fmt(pos?.initial_premium)}
                          </span>
                        </div>
                        <div className="pos-time-row">
                          <div className="pos-cell">
                            <span className="pos-label">开仓时间</span>
                            <span className="pos-value mono">{openTimeLabel}</span>
                          </div>
                          <div className="pos-cell">
                            <span className="pos-label">持仓时长</span>
                            <span className="pos-value mono">
                              {holdDurationLabel}
                            </span>
                          </div>
                        </div>
                      </div>
                    </div>

                    <div className="pos-card">
                      <div className="pos-card-head">
                        <div className="pos-card-symbol">
                          <strong>{pos?.option2_inst_id || "Put"}</strong>
                          <span className="pos-side-badge pos-side-long">
                            看跌 Put
                          </span>
                        </div>
                        <div
                          className={`pos-countdown mono ${countdownUrgent ? "pos-countdown-urgent" : ""}`}
                          title="到期倒计时"
                        >
                          {countdown}
                        </div>
                      </div>
                      <div className="pos-meta">
                        <span className="pos-meta-item">期期 · Put</span>
                        <span className="pos-meta-item">
                          到期 {pos?.expiry_ymd || "—"}
                        </span>
                        <span className="pos-meta-item">
                          行权{" "}
                          {pos?.strike2 != null
                            ? Math.round(Number(pos.strike2))
                            : "—"}
                        </span>
                        <span className="pos-meta-item mono">
                          {fmt(
                            pos?.option2_qty_eth ??
                              plan?.oo_put_qty_eth ??
                              plan?.risk_sizing_preview?.put_qty_eth,
                            2,
                          )}{" "}
                          ETH · {fmt(pos?.option2_qty_contracts, 0)} 张
                        </span>
                        <span
                          className="pos-meta-item mono"
                          title="开仓指数÷开仓均价"
                        >
                          杠杆{" "}
                          {pos?.option2_leverage != null
                            ? `${fmt(pos.option2_leverage, 0)}x`
                            : optionLevLabel(
                                pos?.entry_index_px ?? snap?.index_px,
                                pos?.option2_entry_px,
                              )}
                        </span>
                      </div>
                      <div className="pos-grid">
                        <div className="pos-cell">
                          <span className="pos-label">开仓均价</span>
                          <span className="pos-value mono">
                            {fmtExPx("option", pos?.option2_entry_px)}
                          </span>
                        </div>
                        <div className="pos-cell">
                          <span className="pos-label">买一</span>
                          <span className="pos-value mono">
                            {fmtBidLiqEx(
                              "option",
                              pos?.option2_mark_px ?? snap?.put?.bid,
                              pos?.option2_bid_sz ?? snap?.put?.bid_sz,
                            )}
                          </span>
                        </div>
                        <div className="pos-cell">
                          <span className="pos-label">
                            {plan?.mode === "LIVE" ? "净盈亏" : "浮盈亏"}
                          </span>
                          <span
                            className={`pos-value mono ${pnlClass(pos?.option2_upl)}`}
                          >
                            {fmt(pos?.option2_upl)}
                          </span>
                        </div>
                        <div className="pos-cell">
                          <span className="pos-label">初始权利金</span>
                          <span className="pos-value mono">
                            {fmt(pos?.initial_premium2)}
                          </span>
                        </div>
                        <div className="pos-time-row">
                          <div className="pos-cell">
                            <span className="pos-label">开仓时间</span>
                            <span className="pos-value mono">{openTimeLabel}</span>
                          </div>
                          <div className="pos-cell">
                            <span className="pos-label">持仓时长</span>
                            <span className="pos-value mono">
                              {holdDurationLabel}
                            </span>
                          </div>
                        </div>
                      </div>
                    </div>
                  </>
                ) : (
                  <>
                    <div className="pos-card">
                      <div className="pos-card-head">
                        <div className="pos-card-symbol">
                          <strong>{pos?.perp_inst_id || "ETH-USDT-SWAP"}</strong>
                          <span
                            className={
                              pos?.perp_side === "long"
                                ? "pos-side-badge pos-side-long"
                                : "pos-side-badge pos-side-short"
                            }
                          >
                            {pos?.perp_side === "long" ? "做多" : "做空"}
                          </span>
                        </div>
                      </div>
                      <div className="pos-meta">
                        <span className="pos-meta-item">永续持仓</span>
                        <span className="pos-meta-item">组 {pos?.group_id}</span>
                        <span className="pos-meta-item mono">
                          数量 {fmt(pos?.perp_qty_eth, 2)} ETH
                        </span>
                        <span className="pos-meta-item mono">
                          {fmt(pos?.leverage ?? plan?.leverage, 0)}x
                        </span>
                      </div>
                      <div className="pos-grid">
                        <div className="pos-cell">
                          <span className="pos-label">开仓价</span>
                          <span className="pos-value mono">
                            {fmtExPx("perp", pos?.perp_entry_px)}
                          </span>
                        </div>
                        <div className="pos-cell">
                          <span className="pos-label">可平价</span>
                          <span className="pos-value mono">
                            {fmtExPx("perp", pos?.perp_mark_px)}
                          </span>
                        </div>
                        <div className="pos-cell">
                          <span className="pos-label">浮盈亏</span>
                          <span
                            className={`pos-value mono ${pnlClass(pos?.perp_upl)}`}
                          >
                            {fmt(pos?.perp_upl)}
                          </span>
                        </div>
                        <div className="pos-cell">
                          <span className="pos-label">保证金</span>
                          <span className="pos-value mono">
                            {fmt(pos?.perp_margin)}
                          </span>
                        </div>
                        <div className="pos-cell">
                          <span className="pos-label">名义价值</span>
                          <span className="pos-value mono">
                            {fmt(pos?.perp_notional)}
                          </span>
                        </div>
                        <div className="pos-cell">
                          <span className="pos-label">波动比例</span>
                          <span className="pos-value mono">
                            {fmt(movePct, 2)}%
                          </span>
                        </div>
                        <div className="pos-time-row">
                          <div className="pos-cell">
                            <span className="pos-label">开仓时间</span>
                            <span className="pos-value mono">{openTimeLabel}</span>
                          </div>
                          <div className="pos-cell">
                            <span className="pos-label">持仓时长</span>
                            <span className="pos-value mono">
                              {holdDurationLabel}
                            </span>
                          </div>
                        </div>
                      </div>
                    </div>

                    <div className="pos-card">
                      <div className="pos-card-head">
                        <div className="pos-card-symbol">
                          <strong>{pos?.option_inst_id || "期权"}</strong>
                          <span
                            className={
                              pos?.option_side === "call"
                                ? "pos-side-badge pos-side-long"
                                : "pos-side-badge pos-side-short"
                            }
                          >
                            {pos?.option_side === "call"
                              ? "看涨 Call"
                              : "看跌 Put"}
                          </span>
                        </div>
                        <div
                          className={`pos-countdown mono ${countdownUrgent ? "pos-countdown-urgent" : ""}`}
                          title="到期倒计时"
                        >
                          {countdown}
                        </div>
                      </div>
                      <div className="pos-meta">
                        <span className="pos-meta-item">期权持仓</span>
                        <span className="pos-meta-item">
                          到期 {pos?.expiry_ymd || "—"}
                        </span>
                        <span className="pos-meta-item">
                          行权{" "}
                          {pos?.strike != null
                            ? Math.round(Number(pos.strike))
                            : "—"}
                        </span>
                        <span className="pos-meta-item mono">
                          {fmt(pos?.option_qty_eth, 2)} ETH ·{" "}
                          {fmt(pos?.option_qty_contracts, 0)} 张
                        </span>
                        <span
                          className="pos-meta-item mono"
                          title="开仓指数÷开仓均价"
                        >
                          杠杆 {fmt(pos?.option_leverage, 0)}x
                        </span>
                      </div>
                      <div className="pos-grid">
                        <div className="pos-cell">
                          <span className="pos-label">开仓均价</span>
                          <span className="pos-value mono">
                            {fmtExPx("option", pos?.option_entry_px)}
                          </span>
                        </div>
                        <div className="pos-cell">
                          <span className="pos-label">买一</span>
                          <span className="pos-value mono">
                            {fmtBidLiqEx(
                              "option",
                              pos?.option_mark_px,
                              pos?.option_bid_sz,
                            )}
                          </span>
                        </div>
                        <div className="pos-cell">
                          <span className="pos-label">
                            {plan?.mode === "LIVE" ? "净盈亏" : "浮盈亏"}
                          </span>
                          <span
                            className={`pos-value mono ${pnlClass(pos?.option_upl)}`}
                          >
                            {fmt(pos?.option_upl)}
                          </span>
                        </div>
                        <div className="pos-cell">
                          <span className="pos-label">初始权利金</span>
                          <span className="pos-value mono">
                            {fmt(pos?.initial_premium)}
                          </span>
                        </div>
                        <div className="pos-time-row">
                          <div className="pos-cell">
                            <span className="pos-label">开仓时间</span>
                            <span className="pos-value mono">{openTimeLabel}</span>
                          </div>
                          <div className="pos-cell">
                            <span className="pos-label">持仓时长</span>
                            <span className="pos-value mono">
                              {holdDurationLabel}
                            </span>
                          </div>
                        </div>
                      </div>
                    </div>
                  </>
                )}
              </>
            ) : isOo ? (
              <>
                <div className="pos-empty">Call 持仓 · 暂无</div>
                <div className="pos-empty">Put 持仓 · 暂无</div>
              </>
            ) : (
              <>
                <div className="pos-empty">永续持仓 · 暂无</div>
                <div className="pos-empty">期权持仓 · 暂无</div>
              </>
            )}
          </div>
        </div>

        <div className="plan-market">
          {showAmpCard ? (
          <div className="card plan-card-compact">
            <h3 className="plan-panel-title">指数 / 振幅</h3>
            <div className="kv">
              <span>指数</span>
              <span className="mono">{fmtExPx("index", snap?.index_px)}</span>
            </div>
            <div className="kv">
              <span>
                高点
                {snap?.oo_amplitude?.hours != null
                  ? `（${fmt(snap.oo_amplitude.hours, 0)}h）`
                  : ""}
              </span>
              <span className="mono">
                {fmtExPx("index", snap?.oo_amplitude?.high)}
              </span>
            </div>
            <div className="kv">
              <span>低点</span>
              <span className="mono">
                {fmtExPx("index", snap?.oo_amplitude?.low)}
              </span>
            </div>
            <div className="kv">
              <span>振幅</span>
              <span className="mono">
                {snap?.oo_amplitude?.range_pct != null
                  ? `${fmt(snap.oo_amplitude.range_pct, 2)}%`
                  : "—"}
                {(snap?.oo_amplitude?.max_pct ?? snap?.oo_amplitude?.min_pct) !=
                null
                  ? ` · 上限≤${fmt(
                      Number(
                        snap.oo_amplitude?.max_pct ??
                          snap.oo_amplitude?.min_pct,
                      ),
                      1,
                    )}%`
                  : ""}
                {snap?.oo_amplitude?.filter_enabled === false
                  ? " · 过滤关"
                  : snap?.oo_amplitude?.ok === false
                    ? " · 超限"
                    : snap?.oo_amplitude?.ok === true
                      ? " · 可开"
                      : ""}
              </span>
            </div>
          </div>
          ) : null}
          {!isOo ? (
            <div className="card">
              <h3 className="plan-panel-title">永续行情</h3>
              <div className="kv">
                <span>买一</span>
                <span className="mono">{fmtExPx("perp", snap?.perp?.bid)}</span>
              </div>
              <div className="kv">
                <span>卖一</span>
                <span className="mono">{fmtExPx("perp", snap?.perp?.ask)}</span>
              </div>
              <div className="kv">
                <span>市价</span>
                <span className="mono">
                  {fmtExPx(
                    "perp",
                    snap?.perp?.mark_px ??
                      (snap?.perp?.bid != null && snap?.perp?.ask != null
                        ? (snap.perp.bid + snap.perp.ask) / 2
                        : null),
                  )}
                </span>
              </div>
            </div>
          ) : null}
          <div className="card">
            <h3 className="plan-panel-title">
              {isOo
                ? open
                  ? "持仓虚值"
                  : "期期虚值"
                : open
                  ? "持仓期权"
                  : "期权 ATM"}
              {isOo && snap?.pair
                ? ` · C@${Math.round(
                    Number(
                      snap.pair.call_strike ?? snap.pair.strike ?? 0,
                    ),
                  )} / P@${Math.round(
                    Number(
                      snap.pair.put_strike ?? snap.pair.strike ?? 0,
                    ),
                  )}`
                : snap?.pair
                  ? ` @ ${
                      snap.pair.strike != null
                        ? Math.round(Number(snap.pair.strike))
                        : "—"
                    }`
                  : ""}
            </h3>
            {(() => {
              const under =
                snap?.index_px ??
                (snap?.perp?.bid != null && snap?.perp?.ask != null
                  ? (snap.perp.bid + snap.perp.ask) / 2
                  : snap?.perp?.mark_px ?? null);
              const callPx = open ? snap?.call?.bid : snap?.call?.ask;
              const callSz = open ? snap?.call?.bid_sz : snap?.call?.ask_sz;
              const putPx = open ? snap?.put?.bid : snap?.put?.ask;
              const putSz = open ? snap?.put?.bid_sz : snap?.put?.ask_sz;
              const sideLabel = open ? "买一/流动性" : "卖一/流动性";
              const callLev = optionLevLabel(under, callPx);
              const putLev = optionLevLabel(under, putPx);
              const heldSide = String(pos?.option_side || "").toLowerCase();
              const heldLev =
                open && pos?.option_leverage != null
                  ? `${fmt(pos.option_leverage, 0)}x`
                  : open && heldSide === "call"
                    ? callLev
                    : open && heldSide === "put"
                      ? putLev
                      : null;
              const callK = snap?.pair?.call_strike ?? snap?.pair?.strike;
              const putK = snap?.pair?.put_strike ?? snap?.pair?.strike;
              return (
                <>
                  <div className="kv">
                    <span>
                      Call {sideLabel}
                      {isOo && callK != null
                        ? ` @${Math.round(Number(callK))}`
                        : ""}
                    </span>
                    <span className="mono">
                      {fmtTopEx("option", callPx, callSz)}
                      {!open ? ` · 杠杆 ${callLev}` : ""}
                    </span>
                  </div>
                  <div className="kv">
                    <span>
                      Put {sideLabel}
                      {isOo && putK != null
                        ? ` @${Math.round(Number(putK))}`
                        : ""}
                    </span>
                    <span className="mono">
                      {fmtTopEx("option", putPx, putSz)}
                      {!open ? ` · 杠杆 ${putLev}` : ""}
                    </span>
                  </div>
                  <div className="kv">
                    <span>实际杠杆</span>
                    <span className="mono">
                      {open && !isOo
                        ? heldLev ||
                          (heldSide
                            ? "—"
                            : `Call ${callLev} · Put ${putLev}`)
                        : `Call ${callLev} · Put ${putLev}`}
                    </span>
                  </div>
                </>
              );
            })()}
            {isOo ? (
              <div className="kv">
                <span>相对现价</span>
                <span className="mono">
                  {(() => {
                    const px = snap?.index_px;
                    const ck = snap?.pair?.call_strike ?? snap?.pair?.strike;
                    const pk = snap?.pair?.put_strike;
                    if (px == null || ck == null) return "—";
                    const cOff = Number(ck) - Number(px);
                    const pOff =
                      pk != null ? Number(pk) - Number(px) : null;
                    const fmtOff = (n: number) =>
                      `${n > 0 ? "+" : ""}${n.toFixed(0)}`;
                    return pOff != null
                      ? `C ${fmtOff(cOff)} · P ${fmtOff(pOff)}`
                      : `C ${fmtOff(cOff)}`;
                  })()}
                </span>
              </div>
            ) : (
              <div className="kv">
                <span>距现价</span>
                <span className="mono">
                  {(() => {
                    const strike = snap?.pair?.strike;
                    const px =
                      snap?.index_px ??
                      (snap?.perp?.bid != null && snap?.perp?.ask != null
                        ? (snap.perp.bid + snap.perp.ask) / 2
                        : null);
                    if (strike == null || px == null) return "—";
                    const abs = Math.abs(strike - px);
                    const sign = strike - px > 0 ? "+" : "";
                    const delta = `${sign}${(strike - px).toFixed(0)}`;
                    if (!plan?.atm_open_offset_enabled) {
                      return `${delta} · 偏差限制关`;
                    }
                    const lim = plan?.max_atm_open_offset ?? 3;
                    const ok = abs <= lim + 1e-9;
                    return `${delta} · 开仓${ok ? "可" : "不可"}(|Δ|≤${lim})`;
                  })()}
                </span>
              </div>
            )}
            <div className="kv">
              <span>到期</span>
              <span className="mono">{snap?.pair?.expiry_ymd || "—"}</span>
            </div>
          </div>
        </div>

        {plan?.residuals && plan.residuals.length > 0 ? (
          <div className="card plan-residual-block">
            <div className="plan-residual-head">
              <h4>残留期权</h4>
              <span className="meta">
                自动平仓仍要求权利金≥设定比例；「平仓」仅验流动性
              </span>
            </div>
            <div className="plan-residual-wrap">
              <table className="plan-residual-table">
                <thead>
                  <tr>
                    <th>组名</th>
                    <th>期权名</th>
                    <th>数量</th>
                    <th>开仓权利金</th>
                    <th title="最新买一价格 / 买一数量(ETH)">买一流动性</th>
                    <th title="买一价 × 持仓数量">买一权利金</th>
                    <th title="买一权利金 ÷ 开仓权利金">占比</th>
                    <th>操作</th>
                  </tr>
                </thead>
                <tbody>
                  {plan.residuals.map((r) => {
                    const gid = String(r.group_id || "");
                    const inst = String(r.option_inst_id || "—");
                    const liqOk = r.liquidity_ok === true;
                    return (
                      <tr key={`${gid}:${inst}`}>
                        <td className="mono">{gid || "—"}</td>
                        <td className="mono">{inst}</td>
                        <td className="mono">{fmt(r.option_qty_eth, 2)}</td>
                        <td className="mono">{fmt(r.initial_premium, 2)}</td>
                        <td
                          className="mono"
                          title={
                            liqOk
                              ? "买一深度可覆盖持仓"
                              : "买一不足或盘口不可用"
                          }
                        >
                          {fmtBidLiquidity(r.bid_px, r.bid_sz_eth)}
                        </td>
                        <td className="mono">{fmt(r.current_premium, 2)}</td>
                        <td className="mono">
                          {r.recovery_pct == null
                            ? "—"
                            : `${fmt(r.recovery_pct, 1)}%`}
                        </td>
                        <td>
                          <button
                            type="button"
                            className="btn ghost"
                            style={{ padding: "4px 10px", fontSize: 12 }}
                            disabled={!!residualBusy || !!busy || !liqOk}
                            title={
                              liqOk
                                ? "按最新买一 IOC 平仓（不验权利金比例）"
                                : "买一流动性不足或盘口不可用"
                            }
                            onClick={() => void closeResidual(gid, inst)}
                          >
                            {residualBusy === gid ? "平仓中…" : "平仓"}
                          </button>
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            </div>
          </div>
        ) : null}
      </div>
      ) : null}
    </div>
  );
}
