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' + f"{mm('★ ETH 永续 + 期权对冲', mm_flat())}\n" + "\n", + encoding="utf-8", + ) + print("OK", xmind_path) + print("OK", mm_path) + print("Root marker: star (星号)") + + +if __name__ == "__main__": + main()