Files
eth_hedge_sim/scripts/gen_strategy_xmind.py
T
dekun 4994aaab16 Implement dual target-close paths and residual option expiry.
Document and enforce: A full dual-leg close, B perp-only when deep OTM with residual archive that does not block next open, and expiry settlement when target is missed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 18:32:05 +08:00

635 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 ≈ 18002200",
[
"保证金 ≈ 600750",
"权利金 ≈ 1560",
"费用缓冲 2050",
"波动缓冲 300800",
],
),
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%常远虚",
"期权归档到期 · 不再盯盘",
"逐仓不挡下一组开仓",
"下一组不扫描残留期权",
],
),
G(
"到期平仓",
[
"目标未达标 → 持有到到期",
"期权按内在价值结算",
"活跃组若有永续一并平",
"残留组只结归档期权",
],
),
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('"', "&quot;")
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 xmlmm 另建短树
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组 · 残留可共存",
"远虚归档 · 不挡新开",
"看净利 · 不报复",
"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()