b0dca59681
Co-authored-by: Cursor <cursoragent@cursor.com>
641 lines
22 KiB
Python
641 lines
22 KiB
Python
#!/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'<style id="{sid}" type="topic"><topic-properties '
|
||
f'shape-class="{shape}" svg:fill="{fill}" '
|
||
f'border-line-color="{border}" border-line-width="2pt" '
|
||
f'fo:color="{font}" fo:font-family="Microsoft YaHei UI,Microsoft YaHei,SansSerif" '
|
||
f'fo:font-size="{size}" fo:font-weight="{weight}" '
|
||
f'line-class="org.xmind.branchConnection.roundedelbow" '
|
||
f'line-color="{border}" line-width="2pt" '
|
||
f'margin-top="4pt" margin-bottom="4pt" '
|
||
f'margin-left="12pt" margin-right="12pt"/>'
|
||
f"</style>"
|
||
)
|
||
|
||
|
||
def styles_xml() -> str:
|
||
chunks = [
|
||
'<?xml version="1.0" encoding="UTF-8" standalone="no"?>',
|
||
'<xmap-styles xmlns="urn:xmind:xmap:xmlns:style:2.0"',
|
||
' xmlns:fo="http://www.w3.org/1999/XSL/Format"',
|
||
' xmlns:svg="http://www.w3.org/2000/svg" version="2.0">',
|
||
" <automatic-styles>",
|
||
' <style id="map-luxury" type="map">',
|
||
' <map-properties multi-line-colors="#C9A227 #1F4E79 #1E8449 #B9770E #6C3483 #C0392B #566573"',
|
||
' color-gradient="none" line-tapered="none" svg:fill="#EEF2F6"/>',
|
||
" </style>",
|
||
" </automatic-styles>",
|
||
" <styles>",
|
||
" "
|
||
+ 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(" </styles>")
|
||
chunks.append("</xmap-styles>\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"<title>{xu.escape(title)}</title>"
|
||
if marker:
|
||
body += f'<marker-refs><marker-ref marker-id="{marker}"/></marker-refs>'
|
||
if children:
|
||
body += (
|
||
'<children><topics type="attached">'
|
||
+ "".join(children)
|
||
+ "</topics></children>"
|
||
)
|
||
return f"<topic {' '.join(attrs)}>{body}</topic>"
|
||
|
||
|
||
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'<node TEXT="{t}"/>'
|
||
return f'<node TEXT="{t}">{"".join(children)}</node>'
|
||
|
||
|
||
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 = (
|
||
'<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'
|
||
'<xmap-content xmlns="urn:xmind:xmap:xmlns:content:2.0" version="2.0">\n'
|
||
f' <sheet id="{sheet_id}" theme="map-luxury" timestamp="0">\n'
|
||
f" {root}\n"
|
||
" <title>策略逻辑图 · 高清版</title>\n"
|
||
" </sheet>\n"
|
||
"</xmap-content>\n"
|
||
)
|
||
meta = (
|
||
'<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'
|
||
'<meta xmlns="urn:xmind:xmap:xmlns:meta:2.0" version="2.0">\n'
|
||
" <Author>eth_hedge_sim</Author>\n"
|
||
"</meta>\n"
|
||
)
|
||
manifest = (
|
||
'<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'
|
||
'<manifest xmlns="urn:xmind:xmap:xmlns:manifest:1.0">\n'
|
||
' <file-entry full-path="content.xml" media-type="text/xml"/>\n'
|
||
' <file-entry full-path="styles.xml" media-type="text/xml"/>\n'
|
||
' <file-entry full-path="meta.xml" media-type="text/xml"/>\n'
|
||
' <file-entry full-path="META-INF/" media-type=""/>\n'
|
||
' <file-entry full-path="META-INF/manifest.xml" media-type="text/xml"/>\n'
|
||
"</manifest>\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(
|
||
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||
'<map version="1.0.1">\n'
|
||
f"{mm('★ ETH 永续 + 期权对冲', mm_flat())}\n"
|
||
"</map>\n",
|
||
encoding="utf-8",
|
||
)
|
||
print("OK", xmind_path)
|
||
print("OK", mm_path)
|
||
print("Root marker: star (星号)")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|