-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathmake_workflow.py
More file actions
254 lines (215 loc) · 8.71 KB
/
Copy pathmake_workflow.py
File metadata and controls
254 lines (215 loc) · 8.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
"""生成 README 工作流示意图 (assets/workflow.png).
三列布局 (从左到右):
1. 输入 (Inputs): 期权类型 / 参数 / 数据源 / 对冲策略
2. 引擎 (Engine): HedgeBacktest + MC 路径生成 + 策略调度
3. 输出 (Outputs): 单次回测 / 回测结果对比 / 历史择优三条独立结果路径
使用 matplotlib 手绘, 与项目整体风格 (PALETTE) 保持一致.
用法: python tools/make_workflow.py
"""
from __future__ import annotations
from pathlib import Path
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch, FancyBboxPatch
# ---- 同 gui_app.py PALETTE 对齐 ----
BG = "#F3F5F9"
SURFACE = "#FFFFFF"
BORDER = "#D8DEE8"
TEXT = "#1F2937"
TEXT_MUTED = "#6B7280"
PRIMARY = "#2563EB"
PRIMARY_ACT = "#1E40AF"
PRIMARY_LIT = "#EFF6FF"
ACCENT = "#0EA5E9"
GOLD = "#B8860B"
SUCCESS = "#16A34A"
ROOT = Path(__file__).resolve().parent.parent
ASSETS = ROOT / "assets"
def _pick_cjk_font() -> str:
from matplotlib import font_manager
candidates = [
"PingFang SC", "Heiti SC", "STHeiti", "Hiragino Sans GB",
"Songti SC", "Microsoft YaHei", "SimHei", "SimSun",
"Noto Sans CJK SC", "Source Han Sans SC",
"WenQuanYi Micro Hei", "WenQuanYi Zen Hei",
]
available = {f.name for f in font_manager.fontManager.ttflist}
for c in candidates:
if c in available:
return c
return "DejaVu Sans"
def _card(ax, xy, size, title, items, edge, fill=SURFACE,
title_color="#FFFFFF"):
"""画一张带顶部标题条的圆角卡片.
xy : 左下角
size : (w, h)
title : 顶部标题 (反色)
items : 条目列表 (每个一行)
edge : 主题色 (同时用于标题条底色)
"""
x, y = xy
w, h = size
header_h = 0.9
radius = 0.25
# 卡片主体
body = FancyBboxPatch(
(x, y), w, h,
boxstyle=f"round,pad=0,rounding_size={radius}",
linewidth=1.6, facecolor=fill, edgecolor=edge, zorder=2,
)
ax.add_patch(body)
# 顶部标题条: 先画全宽圆角矩形(作为标题底), 再用普通矩形遮住下半圆角
header_bg = FancyBboxPatch(
(x, y + h - header_h), w, header_h,
boxstyle=f"round,pad=0,rounding_size={radius}",
linewidth=0, facecolor=edge, zorder=3,
)
ax.add_patch(header_bg)
ax.add_patch(mpatches.Rectangle(
(x, y + h - header_h), w, header_h - radius,
linewidth=0, facecolor=edge, zorder=3,
))
ax.text(x + w / 2, y + h - header_h / 2, title,
ha="center", va="center", fontsize=13, fontweight="bold",
color=title_color, zorder=4)
# 条目: 均匀铺开
n = len(items)
inner_top = y + h - header_h - 0.35
inner_bot = y + 0.35
if n > 0:
step = (inner_top - inner_bot) / n
for i, it in enumerate(items):
ty = inner_top - step * (i + 0.5)
ax.text(x + 0.45, ty, it, ha="left", va="center",
fontsize=11, color=TEXT, zorder=4)
def _arrow(ax, xy_from, xy_to, color=PRIMARY_ACT, lw=2.0):
arr = FancyArrowPatch(
xy_from, xy_to,
arrowstyle="-|>,head_length=0.35,head_width=0.28",
linewidth=lw, color=color, zorder=1,
shrinkA=2, shrinkB=2,
)
ax.add_patch(arr)
def render_workflow() -> plt.Figure:
plt.rcParams["font.sans-serif"] = [_pick_cjk_font(), "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False
# 数据坐标系: (0..32) x (0..19)
fig, ax = plt.subplots(figsize=(16, 9.5), dpi=150)
ax.set_xlim(0, 32)
ax.set_ylim(0, 19)
ax.set_aspect("equal")
ax.axis("off")
fig.patch.set_facecolor(BG)
ax.set_facecolor(BG)
# ---- 顶部标题 ----
ax.text(16, 18.1, "DeltaLab 回测工作流",
ha="center", va="center", fontsize=19, fontweight="bold",
color=TEXT)
ax.text(16, 17.2,
"参数配置 → 动态对冲引擎 → 单次结果 / 快照对比 / 历史择优",
ha="center", va="center", fontsize=12, color=TEXT_MUTED)
# 三列顶栏标签
ax.text(5.25, 15.7, "① 配置输入", ha="center", va="center",
fontsize=12, fontweight="bold", color=PRIMARY_ACT)
ax.text(16, 15.7, "② 回测引擎", ha="center", va="center",
fontsize=12, fontweight="bold", color=PRIMARY_ACT)
ax.text(26.75, 15.7, "③ 结果输出", ha="center", va="center",
fontsize=12, fontweight="bold", color=PRIMARY_ACT)
# ================== 左列: 输入 ==================
CARD_W = 8.5
LEFT_X = 1.0
# 期权 类型 & 参数
_card(ax, (LEFT_X, 11.0), (CARD_W, 4.0),
"期权类型 & 参数",
["• 香草 / 累计 / 亚式 / 气囊",
"• 13 种子类型",
"• s0 / K / T / σ / cp / r / q ..."],
edge=PRIMARY)
# 数据源
_card(ax, (LEFT_X, 6.2), (CARD_W, 4.0),
"数据来源",
["• 模拟 (MC) — 种子与采样粒度可选",
"• CSV / Wind — 真实历史行情",
"• 真实行情 bar/日按时间戳自动推导"],
edge=ACCENT)
# 对冲设置
_card(ax, (LEFT_X, 1.4), (CARD_W, 4.0),
"对冲策略 & 触发条件",
["• 每日收盘 (close-to-close)",
"• 每日固定时刻 (真实日内行情)",
"• 固定间隔:绝对 / 相对 / σ 执行口径",
"• 滑点 / 手续费 / 乘数取整"],
edge=GOLD)
# ================== 中列: 引擎 ==================
ENGINE_X = 11.5
ENGINE_W = 9.0
_card(ax, (ENGINE_X, 4.5), (ENGINE_W, 10.5),
"HedgeBacktest 引擎",
["• GBM 路径 + 反对称采样",
"• per-path MC seed",
"• Δ / Γ / ν / Θ / ρ (有限差分)",
"• 每日收盘 / 固定时刻 / 固定间隔调度",
"• 绝对价 / 相对价 / σ 参考点换算",
"• 模拟可选采样;真实行情自动推导 bar/日",
"• 滑点 · 手续费 · 乘数取整",
"• run() 单路径 + run_multi() 并行",
"• S_ref → S_real 自动缩放"],
edge=PRIMARY_ACT, fill=PRIMARY_LIT)
# ================== 右列: 输出 ==================
RIGHT_X = 22.5
_card(ax, (RIGHT_X, 11.0), (CARD_W, 4.0),
"单次回测结果",
["• 回测摘要 (盈亏分解 + Greeks)",
"• 对冲图表 (6 宫格)",
"• 波动率 / 多路径 / 结构 / 明细",
"• 可显式保留为普通快照"],
edge=SUCCESS)
_card(ax, (RIGHT_X, 6.2), (CARD_W, 4.0),
"回测结果对比",
["• 只管理已保存的普通回测快照",
"• 勾选曲线 · 排名 · 实时差异",
"• 不混入历史滚动汇总"],
edge=SUCCESS)
_card(ax, (RIGHT_X, 1.4), (CARD_W, 4.0),
"历史择优 (独立页)",
["• 仅 CSV / Wind 真实历史",
"• 策略开关 · 时刻 / σ 候选独立配置",
"• 近周 / 月 / 季 / 年滚动排名",
"• 应用选中策略到回测",
"• 当前路径验证并保留到对比"],
edge=SUCCESS)
# ================== 箭头 ==================
# 左 -> 引擎: 每张卡片右中 → 引擎左侧对应 y
left_right_x = LEFT_X + CARD_W
engine_left_x = ENGINE_X
for y_src, y_dst in [(13.0, 12.5), (8.2, 9.75), (3.4, 7.0)]:
_arrow(ax, (left_right_x + 0.1, y_src),
(engine_left_x - 0.1, y_dst))
# 引擎 -> 右: 引擎右侧对应 y → 每张卡片左中
engine_right_x = ENGINE_X + ENGINE_W
right_left_x = RIGHT_X
for y_src, y_dst in [(12.5, 13.0), (9.75, 8.2), (7.0, 3.4)]:
_arrow(ax, (engine_right_x + 0.1, y_src),
(right_left_x - 0.1, y_dst))
# 历史推荐必须先在当前路径验证,成功后才进入普通快照结果池。
_arrow(ax, (26.75, 5.45), (26.75, 6.15), color=GOLD, lw=1.6)
ax.text(29.0, 5.8, "验证后保留", ha="center", va="center",
fontsize=8.5, color=GOLD)
# 底部脚注
ax.text(16, 0.55,
"配置定义: gui_app.py:OPTION_CLASSES · "
"引擎: pricing/hedge_backtest.py · "
"GUI 入口: gui_app.py:main()",
ha="center", va="center", fontsize=10,
color=TEXT_MUTED, style="italic")
fig.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.02)
return fig
def main() -> None:
ASSETS.mkdir(parents=True, exist_ok=True)
fig = render_workflow()
out = ASSETS / "workflow.png"
fig.savefig(out, facecolor=BG, bbox_inches="tight", pad_inches=0.3,
dpi=150)
print(f"wrote {out}")
if __name__ == "__main__":
main()