#!/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(
"目标A · 双腿全平",
[
"净利达标 + 期权非远虚",
"先期权后永续",
"流动性闸门:深度 + 标记偏差30%",
],
),
G(
"目标B · 只平永续",
[
"净利达标 + 期权远虚(内在约0)",
"100x杠杆下波动约1%常远虚",
"期权归档到期 · 不再盯盘",
"逐仓不挡下一组开仓",
"下一组不扫描残留期权",
"下方盘口切回新ATM",
"到期按内在价值结算",
],
),
G(
"到期平仓",
[
"目标未达标 → 持有到到期",
"期权按内在价值结算",
"活跃组若有永续一并平",
"残留组只结归档期权",
],
),
G(
"页面监控分工",
[
"有活跃仓 → 钉持仓行权价",
"空仓 → 跟现价ATM(≥5点切换)",
"仅残留 → 列表显示归档腿",
"仅残留 → 下方仍是新ATM",
"浮盈亏只用持仓合约买一",
],
),
L("另有:紧急全平 / 手动平仓"),
],
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 ["目标A双腿", "目标B只平永续", "到期结算", "监控分工"]]),
mm("关键参数", [mm(x) for x in ["仓位/杠杆/费率", "目标/休息/周末", "选约/偏差"]]),
],
),
mm("⑥ 风险评估", [mm(x) for x in ["横盘磨损", "SIM≠实盘", "方向/流动性/保证金", "残留不挡新开"]]),
mm("⑦ 纪律要求", [mm(x) for x in [
"活跃1组 · 残留可共存",
"远虚归档 · 不挡新开",
"有仓钉持仓 · 空仓跟ATM",
"残留列表 · 下方新ATM",
"看净利 · 不报复",
"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()