diff --git a/docs/ETH对冲策略说明.mm b/docs/ETH对冲策略说明.mm
new file mode 100644
index 0000000..12dcae5
--- /dev/null
+++ b/docs/ETH对冲策略说明.mm
@@ -0,0 +1,4 @@
+
+
diff --git a/docs/ETH对冲策略说明.xmind b/docs/ETH对冲策略说明.xmind
new file mode 100644
index 0000000..565bbe0
Binary files /dev/null and b/docs/ETH对冲策略说明.xmind differ
diff --git a/scripts/gen_strategy_xmind.py b/scripts/gen_strategy_xmind.py
new file mode 100644
index 0000000..f6e2928
--- /dev/null
+++ b/scripts/gen_strategy_xmind.py
@@ -0,0 +1,640 @@
+#!/usr/bin/env python3
+"""高逼格 XMind 8 逻辑图:大字号、短文案、深蓝金配色。"""
+
+from __future__ import annotations
+
+import uuid
+import zipfile
+import xml.sax.saxutils as xu
+from pathlib import Path
+
+OUT_DIR = Path(__file__).resolve().parents[1] / "docs"
+
+# 商务色:午夜蓝 + 香槟金 + 冷灰层级
+C = {
+ "root": dict(
+ fill="#0B1F3A",
+ border="#C9A227",
+ font="#F5F0E6",
+ size="20pt",
+ weight="700",
+ shape="org.xmind.topicShape.roundedRect",
+ ),
+ "L1": dict(size="14pt", weight="700", shape="org.xmind.topicShape.roundedRect"),
+ "L2": dict(size="12pt", weight="600", shape="org.xmind.topicShape.roundedRect"),
+ "L3": dict(size="11pt", weight="600", shape="org.xmind.topicShape.roundedRect"),
+ "leaf": dict(
+ fill="#FFFFFF",
+ border="#E6EAF0",
+ font="#2C3E50",
+ size="11pt",
+ weight="400",
+ shape="org.xmind.topicShape.roundedRect",
+ ),
+}
+
+SECTIONS = [
+ dict(
+ key="option",
+ title="① 期权",
+ marker="flag-red",
+ fill="#FFF5F5",
+ border="#C0392B",
+ font="#7B241C",
+ kids="option",
+ ),
+ dict(
+ key="perp",
+ title="② 永续合约",
+ marker="flag-blue",
+ fill="#F4F8FC",
+ border="#1F4E79",
+ font="#1A365D",
+ kids="perp",
+ ),
+ dict(
+ key="design",
+ title="③ 设计理念",
+ marker="flag-purple",
+ fill="#F8F5FB",
+ border="#6C3483",
+ font="#4A235A",
+ kids="design",
+ ),
+ dict(
+ key="capital",
+ title="④ 资金要求",
+ marker="flag-green",
+ fill="#F3FAF6",
+ border="#1E8449",
+ font="#145A32",
+ kids="capital",
+ ),
+ dict(
+ key="ops",
+ title="⑤ 操盘思路",
+ marker="flag-orange",
+ fill="#FFFBF2",
+ border="#B9770E",
+ font="#7E5109",
+ kids="ops",
+ ),
+ dict(
+ key="risk",
+ title="⑥ 风险评估",
+ marker="flag-yellow",
+ fill="#FFFCF2",
+ border="#B7950B",
+ font="#7D6608",
+ kids="risk",
+ ),
+ dict(
+ key="discipline",
+ title="⑦ 纪律要求",
+ marker="people-blue",
+ fill="#F4F6F7",
+ border="#566573",
+ font="#2C3E50",
+ kids="discipline",
+ ),
+]
+
+
+def nid() -> str:
+ return uuid.uuid4().hex[:16]
+
+
+def sty(sid: str, fill: str, border: str, font: str, size: str, weight: str, shape: str) -> str:
+ return (
+ f'"
+ )
+
+
+def styles_xml() -> str:
+ chunks = [
+ '',
+ '',
+ " ",
+ ' ",
+ " ",
+ " ",
+ " "
+ + sty(
+ "sty-root",
+ C["root"]["fill"],
+ C["root"]["border"],
+ C["root"]["font"],
+ C["root"]["size"],
+ C["root"]["weight"],
+ C["root"]["shape"],
+ ),
+ " "
+ + sty(
+ "sty-leaf",
+ C["leaf"]["fill"],
+ C["leaf"]["border"],
+ C["leaf"]["font"],
+ C["leaf"]["size"],
+ C["leaf"]["weight"],
+ C["leaf"]["shape"],
+ ),
+ " "
+ + sty("sty-pro", "#EAF7EF", "#1E8449", "#145A32", "12pt", "600", C["L2"]["shape"]),
+ " "
+ + sty("sty-con", "#FDEDEC", "#C0392B", "#7B241C", "12pt", "600", C["L2"]["shape"]),
+ " "
+ + sty("sty-sub", "#FFFFFF", "#C9A227", "#0B1F3A", "12pt", "600", C["L2"]["shape"]),
+ ]
+ for s in SECTIONS:
+ chunks.append(
+ " "
+ + sty(
+ f"sty-{s['key']}",
+ s["fill"],
+ s["border"],
+ s["font"],
+ C["L1"]["size"],
+ C["L1"]["weight"],
+ C["L1"]["shape"],
+ )
+ )
+ # 操盘二级强调色
+ for key, fill, border, font in [
+ ("ops_mind", "#FFF8E1", "#F4D03F", "#7D6608"),
+ ("ops_pos", "#E8F6F3", "#16A085", "#0E6655"),
+ ("ops_open", "#E9F7EF", "#27AE60", "#196F3D"),
+ ("ops_close", "#F5EEF8", "#8E44AD", "#5B2C6F"),
+ ("ops_param", "#EBF5FB", "#2980B9", "#1A5276"),
+ ]:
+ chunks.append(
+ " " + sty(f"sty-{key}", fill, border, font, "12pt", "600", C["L2"]["shape"])
+ )
+ chunks.append(" ")
+ chunks.append("\n")
+ return "\n".join(chunks)
+
+
+def topic(
+ title: str,
+ children: list[str] | None = None,
+ *,
+ style_id: str | None = None,
+ marker: str | None = None,
+ structure_class: str | None = None,
+ tid: str | None = None,
+) -> str:
+ tid = tid or nid()
+ attrs = [f'id="{tid}"']
+ if structure_class:
+ attrs.append(f'structure-class="{structure_class}"')
+ if style_id:
+ attrs.append(f'style-id="{style_id}"')
+ body = f"
{xu.escape(title)}"
+ if marker:
+ body += f''
+ if children:
+ body += (
+ ''
+ + "".join(children)
+ + ""
+ )
+ return f"{body}"
+
+
+def L(text: str) -> str:
+ """叶子:一句一事,便于放大后清晰。"""
+ return topic(text, style_id="sty-leaf")
+
+
+def G(title: str, lines: list[str], style: str = "sub", marker: str | None = None) -> str:
+ return topic(title, [L(x) for x in lines], style_id=f"sty-{style}", marker=marker)
+
+
+def bodies() -> dict[str, list[str]]:
+ return {
+ "option": [
+ G(
+ "优势",
+ [
+ "亏损有界:最多权利金 + 费用",
+ "大波动弹性强,利润空间大",
+ "小资金撬动名义(租金模型)",
+ "按 1 ETH 计价,盈亏直觉清晰",
+ "只做买方,无卖方义务",
+ ],
+ "pro",
+ "symbol-plus",
+ ),
+ G(
+ "劣势",
+ [
+ "横盘磨损:不动也在付租金",
+ "盘口薄,点差与滑点明显",
+ "平仓依赖买一流动性",
+ "ATM 档位粗时偏离现价",
+ "临近到期,盘口易恶化",
+ ],
+ "con",
+ "symbol-minus",
+ ),
+ ],
+ "perp": [
+ G(
+ "优势",
+ [
+ "流动性深,开平接近瞬时",
+ "反向持仓,对冲路径清晰",
+ "杠杆可配(默认 3x)",
+ "与期权组成完整对冲组",
+ ],
+ "pro",
+ "symbol-plus",
+ ),
+ G(
+ "劣势",
+ [
+ "保证金与强平风险",
+ "实盘有资金费率",
+ "急涨急跌挤压保证金",
+ "横盘几乎不帮权利金回本",
+ "固定 1:2,并非精确中性",
+ ],
+ "con",
+ "symbol-minus",
+ ),
+ ],
+ "design": [
+ L("结构:ATM 期权买方 + 反向永续"),
+ L("波动大 → 多轮兑现净利"),
+ L("波动小 → 接受权利金磨损"),
+ L("到期自动全平 · 概率样本思维"),
+ L("吃波动,不赌横盘"),
+ G(
+ "权利金 = 租金",
+ [
+ "统一按 1 ETH 名义核算",
+ "例:20U 租 1870 看涨",
+ "现价到 1970 → 约值 100,毛利约 80",
+ ],
+ ),
+ L("规则确定:周末跳过 · 到期强平"),
+ L("出场看费用后净利,不看虚盈"),
+ L("单组纪律 · 薄腿优先(先期权后永续)"),
+ L("SIM:公共行情 + 本地撮合,不下真单"),
+ ],
+ "capital": [
+ L("默认仓:永续 1 ETH + 期权 2 ETH · 3x"),
+ G(
+ "单组占用(ETH ≈ 1800–2200)",
+ [
+ "保证金 ≈ 600–750",
+ "权利金 ≈ 15–60",
+ "费用缓冲 20–50",
+ "波动缓冲 300–800",
+ ],
+ ),
+ G(
+ "建议权益",
+ [
+ "≥ 1500 仅下限(不推荐)",
+ "≥ 3000 最小可用",
+ "≥ 5000 推荐起步",
+ "≥ 10000 较舒适",
+ ],
+ "pro",
+ "star-green",
+ ),
+ L("仓位减半,资金约减半(费用占比升)"),
+ L("试跑建议:永续 0.1 / 期权 0.2"),
+ L("SIM 权益 10000 ≠ 实盘入金"),
+ L("按磨损日留余量,勿按每组必赚 15"),
+ ],
+ "ops": [
+ topic(
+ "心法与节奏",
+ [
+ L("有波动多做,磨盘日可认亏"),
+ L("不追求每日固定轮次"),
+ L("小目标高频兑现(默认 15U)"),
+ L("磨损按预算接受,不报复加仓"),
+ L("全天可开 · 周末默认停开"),
+ L("全平后休息 5 分钟再开下一组"),
+ L("同时最多 1 组,无日轮次上限"),
+ ],
+ style_id="sty-ops_mind",
+ marker="symbol-attention",
+ ),
+ topic(
+ "仓位结构",
+ [
+ L("永续 1 ETH · 市价 · 3x"),
+ L("期权 2 ETH · 只买"),
+ L("开吃卖一 · 平吃买一"),
+ L("开仓:期权 → 永续(失败回滚)"),
+ L("平仓:期权 → 永续(对冲先留)"),
+ G(
+ "选向(ATM vs 现价)",
+ [
+ "ATM 偏低 → Call + 空永续",
+ "ATM 偏高 → Put + 多永续",
+ "贴平 → 卖一更高侧开仓",
+ "卖一相等 → 等待",
+ ],
+ ),
+ ],
+ style_id="sty-ops_pos",
+ marker="symbol-plus",
+ ),
+ topic(
+ "开仓",
+ [
+ G(
+ "前置",
+ [
+ "策略运行中",
+ "无持仓 · 非休息期",
+ "非周末跳过",
+ ],
+ ),
+ G(
+ "选约",
+ [
+ "剩余时长 ≥ 12h",
+ "取该到期 ATM",
+ "现价/卖一 ≥ 100",
+ "不合格约 → 等待",
+ ],
+ ),
+ G(
+ "执行",
+ [
+ "① 吃卖一开期权",
+ "② 市价开永续",
+ "③ 失败则回滚期权",
+ "④ 锁定初始权利金",
+ ],
+ ),
+ ],
+ style_id="sty-ops_open",
+ marker="arrow-up",
+ ),
+ topic(
+ "平仓",
+ [
+ L("触发即全平 · 先期权后永续"),
+ G(
+ "主出场",
+ [
+ "净利 = 双腿浮盈 − 预估平仓费",
+ "默认目标 ≥ 15 USDT",
+ "或 ≥ 权利金 × 1.0",
+ ],
+ ),
+ G(
+ "流动性闸门",
+ [
+ "买一深度足够",
+ "买一偏离标记 ≤ 30%",
+ "不满足则等待",
+ ],
+ ),
+ G(
+ "到期全平",
+ [
+ "上海 16:00(OKX)",
+ "绕过闸门 · 暂停也执行",
+ "亏权利金 = 预算成本",
+ ],
+ ),
+ L("另有:紧急全平 / 手动平仓"),
+ G(
+ "执行",
+ [
+ "① 确认期权买一",
+ "② 平期权入账",
+ "③ 瞬时平永续",
+ "④ 失败则回滚",
+ ],
+ ),
+ ],
+ style_id="sty-ops_close",
+ marker="arrow-down",
+ ),
+ topic(
+ "关键参数",
+ [
+ L("仓位 1 / 2 · 杠杆 3 · 费率 0.05%"),
+ L("出场 fixed_usdt · 目标 15U"),
+ L("休息 300s · 周末跳过"),
+ L("选约 12h · 杠杆门槛 100"),
+ L("ATM 偏差默认关 · 平仓偏差 30%"),
+ L("行情:OKX 默认 · 币安可切"),
+ ],
+ style_id="sty-ops_param",
+ marker="symbol-question",
+ ),
+ ],
+ "risk": [
+ G(
+ "主要风险",
+ [
+ "高 · 横盘磨损权利金",
+ "高 · SIM 难直接外推实盘",
+ "中高 · 方向选错",
+ "中 · 费用侵蚀小目标",
+ "中 · 期权流动性",
+ "中 · 保证金挤压",
+ "中 · 行情中断 / 限流",
+ ],
+ "con",
+ "symbol-exclam",
+ ),
+ G(
+ "缓解",
+ [
+ "周末跳过 · 到期认亏",
+ "净利达标才兑现",
+ "目标覆盖费用 · 留缓冲",
+ "小资金验证后再放大",
+ ],
+ "pro",
+ "symbol-right",
+ ),
+ G(
+ "边界",
+ [
+ "不做卖方",
+ "不做横盘智能过滤",
+ "不做多组并行",
+ "实盘另需授权确认",
+ ],
+ ),
+ ],
+ "discipline": [
+ L("有仓不开下一组"),
+ L("全平后休息满 5 分钟"),
+ L("周末不新开,持仓可平"),
+ L("常规平仓不硬砸流动性"),
+ L("只看出场净利,不看虚盈"),
+ L("磨损日认账,不报复交易"),
+ L("不临时叠仓、不加卖权"),
+ L("SIM 跑通再谈实盘放大"),
+ ],
+ }
+
+
+def mm_escape(s: str) -> str:
+ return xu.escape(s).replace('"', """)
+
+
+def mm(title: str, children: list[str] | None = None) -> str:
+ t = mm_escape(title)
+ if not children:
+ return f''
+ return f'{"".join(children)}'
+
+
+def mm_flat() -> list[str]:
+ """Freemind:短句版,便于导入后手调样式。"""
+ b = bodies()
+
+ def walk_xml_titles(xml_topics: list[str]) -> list[str]:
+ # bodies 已是 xmind xml;mm 另建短树
+ return []
+
+ _ = walk_xml_titles
+ return [
+ mm(
+ "① 期权",
+ [
+ mm("优势", [mm(x) for x in ["亏损有界", "大波动弹性强", "租金模型", "1 ETH 好算", "只做买方"]]),
+ mm("劣势", [mm(x) for x in ["横盘磨损", "盘口薄", "买一依赖", "ATM 偏离", "临期恶化"]]),
+ ],
+ ),
+ mm(
+ "② 永续合约",
+ [
+ mm("优势", [mm(x) for x in ["流动性深", "对冲清晰", "杠杆可配", "组成对冲组"]]),
+ mm("劣势", [mm(x) for x in ["强平风险", "资金费率", "保证金挤压", "横盘无助", "1:2 非精确"]]),
+ ],
+ ),
+ mm("③ 设计理念", [mm(x) for x in [
+ "ATM 买方 + 反向永续",
+ "波动兑现 · 磨损认账",
+ "到期全平 · 概率样本",
+ "权利金 = 租金",
+ "薄腿优先 · 单组纪律",
+ ]]),
+ mm("④ 资金要求", [mm(x) for x in [
+ "默认 1+2 ETH · 3x",
+ "推荐权益 ≥ 5000",
+ "舒适 ≥ 10000",
+ "试跑 0.1 / 0.2",
+ ]]),
+ mm(
+ "⑤ 操盘思路",
+ [
+ mm("心法与节奏", [mm(x) for x in ["有波动多做", "目标 15U", "休息 5 分钟", "最多 1 组"]]),
+ mm("仓位结构", [mm(x) for x in ["永续 1 · 期权 2", "先期权后永续", "ATM 选向"]]),
+ mm("开仓", [mm(x) for x in ["前置检查", "选约门槛", "四步执行"]]),
+ mm("平仓", [mm(x) for x in ["净利达标", "流动性闸门", "到期全平"]]),
+ mm("关键参数", [mm(x) for x in ["仓位/杠杆/费率", "目标/休息/周末", "选约/偏差"]]),
+ ],
+ ),
+ mm("⑥ 风险评估", [mm(x) for x in ["横盘磨损", "SIM≠实盘", "方向/流动性/保证金", "不做卖方与叠仓"]]),
+ mm("⑦ 纪律要求", [mm(x) for x in [
+ "单组 · 休息 · 周末",
+ "不硬砸 · 看净利",
+ "不报复 · 不叠仓",
+ "SIM 后再放大",
+ ]]),
+ ]
+
+
+def main() -> None:
+ OUT_DIR.mkdir(parents=True, exist_ok=True)
+ data = bodies()
+ branches = []
+ for s in SECTIONS:
+ branches.append(
+ topic(
+ s["title"],
+ data[s["kids"]],
+ style_id=f"sty-{s['key']}",
+ marker=s["marker"],
+ )
+ )
+
+ # 中心主题:星号标记 = XMind marker-id "star"
+ root = topic(
+ "ETH 永续 + 期权对冲",
+ branches,
+ tid=nid(),
+ structure_class="org.xmind.ui.logic.right",
+ style_id="sty-root",
+ marker="star",
+ )
+
+ sheet_id = nid()
+ content = (
+ '\n'
+ '\n'
+ f' \n'
+ f" {root}\n"
+ " 策略逻辑图 · 高清版\n"
+ " \n"
+ "\n"
+ )
+ meta = (
+ '\n'
+ '\n'
+ " eth_hedge_sim\n"
+ "\n"
+ )
+ manifest = (
+ '\n'
+ '\n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ "\n"
+ )
+
+ xmind_path = OUT_DIR / "ETH对冲策略说明.xmind"
+ with zipfile.ZipFile(xmind_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
+ zf.writestr("content.xml", content.encode("utf-8"))
+ zf.writestr("styles.xml", styles_xml().encode("utf-8"))
+ zf.writestr("meta.xml", meta.encode("utf-8"))
+ zf.writestr("META-INF/manifest.xml", manifest.encode("utf-8"))
+
+ mm_path = OUT_DIR / "ETH对冲策略说明.mm"
+ mm_path.write_text(
+ '\n'
+ '\n",
+ encoding="utf-8",
+ )
+ print("OK", xmind_path)
+ print("OK", mm_path)
+ print("Root marker: star (星号)")
+
+
+if __name__ == "__main__":
+ main()