外汇期权、波动率微笑、期限结构……这些复杂的金融数据,如何直观呈现?今天我们用 DolphinDB 内置的 plot() 函数,以外汇期权波动率曲面为例,手把手教你画出专业级的 2D/3D 图表,而且支持鼠标拖拽、缩放、悬停查看数值!
一、plot() 函数核心参数解析
DolphinDB 的 plot() 是一个轻量级但功能强大的绘图函数,无需安装任何第三方库,直接在数据库内完成数据到图形的转换。
plot(data, [labels], title, chartType, [stacking], [extras])
参数详解
| 参数 | 类型 | 说明 |
|---|---|---|
| data | 矩阵 | 列 = 一条线(LINE)或一列刻度(SURFACE);行 = X 轴方向的数据点。 |
| labels | STRING 向量 | X 轴刻度文字(填入则覆盖矩阵的原行标签),长度须等于行数。 |
| title | STRING 向量 | 若标题为标量,则为图标标题,若为矢量,则元素依次为[“标题“,”x轴”,”y轴”,”z轴”] |
| chartType | 常量 | 图表类型:LINE / BAR / PIE / AREA / SCATTER / HISTOGRAM / SURFACE |
| stacking | BOOL | 是否堆叠显示,默认 false。目前仅对 BAR / AREA 有效 |
| extras | DICT | 附加选项,用 {} 字面量传入。仅支持 LINE 系列,且可以再次配置 {multiYAxes: true} 表示支持多个 Y 轴。 |
二、数据准备:外汇期权波动率曲面(USDCNY)
我们基于 DolphinDB 内置的 fxVolatilitySurfaceBuilder 函数,构建 USDCNY 的波动率曲面。
为了图形更平滑,准备13个期限的 ["ATM", "D25_RR", "D25_BF", "D10_RR", "D10_BF"] 的一组期权报价,构建出DolphinDB 中的一个外汇波动率曲面对象 surf 。
refDate = 2025.08.18
ccyPair = "USDCNY"
spot = 7.1627
quoteTerms = ["1d","1w","2w","3w","1M","2M","3M","6M","9M","1y","18M","2y","3y"]
quoteNames = ["ATM","D25_RR","D25_BF","D10_RR","D10_BF"]
rawQuotes = [
0.030000,-0.007500, 0.003500,-0.010000, 0.005500,
...(共 13×5 = 65 个值)
0.044750, 0.006250, 0.003400, 0.009000, 0.008550]
quotes = reshape(rawQuotes, 5:13).transpose() // → 13行×5列矩阵
//构建利率曲线和波动率曲面
domesticCurve = parseMktData(domesticCurveInfo) // CNY IrYieldCurve
foreignCurve = parseMktData(foreignCurveInfo) // USD IrYieldCurve
surf = fxVolatilitySurfaceBuilder(
refDate, ccyPair, quoteNames, quoteTerms, quotes, spot,
domesticCurve, foreignCurve, "SVI")
准备好曲面后,我们就可以开始画图了!
示例 1:2D 折线图(LINE)—— 中美国债利率曲线对比
量化研究中,许多策略需要同时参考国内和国外的利率水平。比如 CNY 利率和 USD 利率,它们的绝对值可能相差很大。如果画在一张图里,小量级的曲线会被压成一条直线,什么都看不清。plot 函数支持有两个轴刻度,从而能重叠清晰地显示两个利率曲线。
这个例子中,我们用 curvePredict 基于已经构建好的外汇波动率曲面对象,在 100 个等间距日期上查询利率值,并用 plot 绘制两条平滑的利率曲线。
// 数据准备
startDate = curveDates[4] // 从 1M 开始,跳过近端噪声
endDate = curveDates[12] // 3Y
nPts = 100
span = endDate - startDate
dates100 = array(DATE, nPts)
for (i in 0:nPts) { dates100[i] = startDate + int(i * span / (nPts - 1.0)) }
cnyDense = curvePredict(domesticCurve, dates100) * 100.0
usdDense = curvePredict(foreignCurve, dates100) * 100.0
dates100 = ...
// 查询这 100 个日期上的利率值(单位:百分比)
cnyDense = curvePredict(domesticCurve, dates100) * 100.0
usdDense = curvePredict(foreignCurve, dates100) * 100.0
//构建矩阵 & 设置标签
labels100 = array(STRING, nPts)
for (i in 0:nPts) { labels100[i] = string(dates100[i]) }
mCurves = matrix(cnyDense, usdDense) // 100行×2列
mCurves.rename!(labels100, ["CNY (%)", "USD (%)"]) //↑ X轴刻度 ↑ 图例名称
// 构建一个矩阵:100 行 × 2 列
mCurves = matrix(cnyDense, usdDense)
// 给行起名字(X 轴刻度)为日期字符串,给列起名字(图例)为 "CNY (%)" 和 "USD (%)"
mCurves.rename!(string(dates100), ["CNY (%)", "USD (%)"])
// 绘图
plot(mCurves, ,
["CNY 与 USD 利率曲线", "日期", "利率 (%)"],
LINE, false, { multiYAxes: true, autoScaleYAxes: true})
示例 2:3D 曲面(SURFACE)—— 按行权价(Strike)构建波动率曲面
外汇期权的隐含波动率不是一成不变的,它会随着到期时间和行权价的变化而呈现出复杂的曲面形状。通过 3D 曲面图,我们能直观清晰地看出整个曲面的地形——哪里高(波动率大)、哪里低(波动率小)。
这个例子中,我们用 optionVolPredict 在 30 个日期 × 21 个 Strike 的网格上查询 630 个 IV 值,绘制 3D 交互曲面。
// 数据准备
nK = 21; nT = 30
denseStrikes = globalKmin + ... * double(0..(nK - 1)) // 21 个等间距 Strike
denseDates = ... // 30 个等间距日期
volMat = optionVolPredict(surf, denseDates, denseStrikes) * 100.0 // 30×21 矩阵
volMat.rename!(dateLabels, strikeLabels)
// ↑ Y轴(行标签) ↑ X轴(列标签)
// 绘图
plot(volMat, ,
["USDCNY 波动率曲面 (Strike, 30×21)", "Strike", "到期日", "隐含波动率 (%)"],
SURFACE)
示例 3:SURFACE 模式 —— 按 Delta 构建波动率曲面
在日常研究中,许多外汇期权交易员习惯用 Delta(期权对即期汇率的敏感度)而不是行权价来观察波动率。因为不同期限的 ATM 行权价不同,用 Delta 可以标准化地对比。
在绘制 Delta 维度的图之前,需要先构建一个 9 点 Delta 网格:10P, 20P, 30P, 40P, ATM, 40C, 30C, 20C, 10C。
| 10P | 20P | 30P | 40P | ATM | 40C | 30C | 20C | 10C | |
|---|---|---|---|---|---|---|---|---|---|
| 1M | 1.85 | 1.79 | 1.75 | 1.72 | 1.69 | 1.67 | 1.64 | 1.60 | 1.55 |
| 3M | 3.83 | 3.40 | 3.23 | 3.16 | 3.14 | 3.15 | 3.18 | 3.25 | 3.38 |
| 1Y | 4.45 | 4.22 | 4.06 | 3.97 | 3.95 | 4.03 | 4.22 | 4.53 | 4.95 |
| 3Y | 4.88 | 4.61 | 4.42 | 4.33 | 4.48 | 4.68 | 4.96 | 5.32 | 5.78 |
我们把 X 轴换成 Delta 值:从 10P 到 ATM 再到 10C,共 9 个点。Y 轴仍然是 9 个期限,Z 轴是隐含波动率。
//绘图
plot(deltaIvMat, ,
["USDCNY 波动率曲面 (Delta, 9×9)", "Delta", "期限", "隐含波动率 (%)"],
SURFACE)
小技巧
deltaIvMat 已通过 rename! 设好标签,因此不需要再传参 labels:
- 行标签 = ["1M","2M",...,"3y"] → Y 轴
- 列标签 = ["10P","20P",...,"10C"] → X 轴
- 矩阵元素值 = IV (%) → Z 轴
同一个 SURFACE 调用模式,只需换 data 和 title 即可切换维度视角。
示例 4:LINE 模式 — Vol vs Strike 多线对比
有时候我们不需要 3D 曲面,只需要挑几个代表性的期限,看它们的波动率微笑曲线(隐含波动率随行权价变化的形状)。这有助于快速比较短期和长期期权的波动率溢价。
这个例子,我们选择 5 个代表期限,每条折线一个期限的 IV 随 Strike 变化。
// 数据准备
pickNames = ["1M","3M","6M","1Y","3Y"]
pickDates = [usedDates[0], usedDates[2], usedDates[3], usedDates[5], usedDates[8]]
strikeMat = optionVolPredict(surf, pickDates, denseStrikes) * 100.0 // 5行×21列
mVolStrike = strikeMat.transpose() // 21行×5列:每列一条折线
mVolStrike.rename!(strikeLabels, pickNames)
// ↑ X轴刻度 ↑ 图例
// 绘图
plot(mVolStrike, ,
["Vol vs Strike 微笑", "Strike", "隐含波动率 (%)"],
LINE, false, { autoScaleYAxes: true })
示例 5:LINE 模式 —— 波动率微笑(Vol vs Delta)
上一张图用 Strike 做 X 轴,不同期限的 ATM 行权价不同,所以曲线在 X 轴上的位置会左右偏移,不便于直接比较“微笑的弯曲程度”。
用 Delta 做 X 轴就没有这个问题:每个期限的 ATM 都对应 Delta=50,所有曲线的中间点都对齐了。这样你可以更纯粹地比较微笑的不对称性(Skew)和曲率(Curvature)。
// 数据准备
pickIdx = [0, 2, 3, 5, 8] // 在 usedTerms 中的索引
mVolDelta = matrix(
flatten(deltaIvMat[pickIdx[0], ]), // 第 0 行(1M)的 9 个 Delta 点
flatten(deltaIvMat[pickIdx[1], ]), // 第 2 行(3M)
flatten(deltaIvMat[pickIdx[2], ]),
flatten(deltaIvMat[pickIdx[3], ]),
flatten(deltaIvMat[pickIdx[4], ]))
mVolDelta.rename!(deltaLabels, pickNames)
// ↑ X轴: 10P...10C ↑ 图例: 1M/3M/6M/1Y/3Y
// 绘图
plot(mVolDelta, ,
["Vol vs Delta 微笑", "Delta", "隐含波动率 (%)"],
LINE, false, { autoScaleYAxes: true })
DolphinDB plot() 特点总结
| 特性 | 它能给你带来什么 |
|---|---|
| 零依赖 | 不用装 Python、不用配 matplotlib,DolphinDB 自带 |
| 金融原生 | 矩阵直接来自因子表、波动率曲面对象,无缝衔接 |
| 交互式 3D | SURFACE 图表支持鼠标拖拽旋转/缩放,适合探索分析 |
| 多 Y 轴 | 解决量级差异大的曲线同图对比难题 |
| 一体化工作流 | 从数据查询 → 建模计算 → 可视化,全在同一个脚本里 |
运行环境要求:DolphinDB 3.00.5 及以上版本。
写在最后
plot() 函数虽小,却体现了 DolphinDB “计算与可视化一体化” 的设计理念。
无论你是做量化回测、风险监控,还是撰写投资分析报告,都可以告别“数据库导 CSV → Python 画图”的繁琐流程,在同一个 DolphinDB 脚本里完成从数据到洞察的全过程。
如果你也想体验 DolphinDB 带来的高效开发体验,欢迎下载社区版试用,或者访问我们的官网网站 dolphindb.cn 查看更多金融案例。
示例代码
// ============================================================
// DolphinDB plot() 可视化完整教程
// 通过外汇波动率曲面演示 LINE / SURFACE 两种图表模式
//
// plot() 函数签名:
// plot(data, [labels], title, chartType, [stacking], [extras])
//
// data 矩阵或表
// LINE: 每列一条折线;行标签→X 轴刻度
// SURFACE: 值=Z 轴高度;行标签→Y 轴,列标签→X 轴
// labels STRING 向量,一般省略(用 rename! 控制标签更方便)
// title STRING 向量
// LINE: 3 元素 [标题, X轴名, Y轴名]
// SURFACE: 4 元素 [标题, X轴名, Y轴名, Z轴名]
// chartType LINE / BAR / PIE / AREA / SCATTER / HISTOGRAM / SURFACE
// stacking BOOL,是否堆叠,默认 false
// extras DICT,可选附加参数:
// multiYAxes: true → 每列独立 Y 轴
// autoScaleYAxes: true → 各 Y 轴自动缩放
//
// 矩阵标签:matrix.rename!(rowLabels, colLabels)
// LINE → rowLabels 成为 X 轴刻度,colLabels 成为图例名
// SURFACE → rowLabels 成为 Y 轴刻度,colLabels 成为 X 轴刻度
// ============================================================
// ── 1. 数据准备 ──────────────────────────────────────────
refDate = 2025.08.18
ccyPair = "USDCNY"
spot = 7.1627
quoteTerms = ["1d","1w","2w","3w","1M","2M","3M","6M","9M","1y","18M","2y","3y"]
quoteNames = ["ATM","D25_RR","D25_BF","D10_RR","D10_BF"]
rawQuotes = [
0.030000,-0.007500, 0.003500,-0.010000, 0.005500,
0.020833,-0.004500, 0.002000,-0.006000, 0.003800,
0.022000,-0.003500, 0.002000,-0.004500, 0.004100,
0.022350,-0.003500, 0.002000,-0.004500, 0.004150,
0.024178,-0.003000, 0.002200,-0.004750, 0.005500,
0.027484,-0.002650, 0.002220,-0.004000, 0.005650,
0.030479,-0.002500, 0.002400,-0.003500, 0.005750,
0.035752,-0.000500, 0.002750, 0.000000, 0.006950,
0.038108, 0.001000, 0.002800, 0.003000, 0.007550,
0.039492, 0.002250, 0.002950, 0.005000, 0.007550,
0.040500, 0.004000, 0.003100, 0.007000, 0.007850,
0.041750, 0.005250, 0.003350, 0.008000, 0.008400,
0.044750, 0.006250, 0.003400, 0.009000, 0.008550]
quotes = reshape(rawQuotes, 5:13).transpose()
curveDates = [2025.08.21, 2025.08.27, 2025.09.03, 2025.09.10, 2025.09.22,
2025.10.20, 2025.11.20, 2026.02.24, 2026.05.20, 2026.08.20,
2027.02.22, 2027.08.20, 2028.08.21]
cnyRates = [1.5113, 1.5402, 1.5660, 1.5574, 1.5556,
1.5655, 1.5703, 1.5934, 1.6040, 1.6020,
1.5928, 1.5842, 1.6068] / 100.0
usdRates = [4.3345, 4.3801, 4.3119, 4.3065, 4.2922,
4.2196, 4.1599, 4.0443, 4.0244, 3.9698,
3.7740, 3.6289, 3.5003] / 100.0
// ── 2. 构建利率曲线 + 波动率曲面 ─────────────────────────
domesticCurveInfo = dict(STRING, ANY)
domesticCurveInfo["mktDataType"] = "Curve"
domesticCurveInfo["curveType"] = "IrYieldCurve"
domesticCurveInfo["referenceDate"] = refDate
domesticCurveInfo["currency"] = "CNY"
domesticCurveInfo["dayCountConvention"] = "Actual365"
domesticCurveInfo["compounding"] = "Continuous"
domesticCurveInfo["interpMethod"] = "Linear"
domesticCurveInfo["extrapMethod"] = "Flat"
domesticCurveInfo["frequency"] = "Annual"
domesticCurveInfo["dates"] = curveDates
domesticCurveInfo["values"] = cnyRates
domesticCurve = parseMktData(domesticCurveInfo)
foreignCurveInfo = dict(STRING, ANY)
foreignCurveInfo["mktDataType"] = "Curve"
foreignCurveInfo["curveType"] = "IrYieldCurve"
foreignCurveInfo["referenceDate"] = refDate
foreignCurveInfo["currency"] = "USD"
foreignCurveInfo["dayCountConvention"] = "Actual365"
foreignCurveInfo["compounding"] = "Continuous"
foreignCurveInfo["interpMethod"] = "Linear"
foreignCurveInfo["extrapMethod"] = "Flat"
foreignCurveInfo["frequency"] = "Annual"
foreignCurveInfo["dates"] = curveDates
foreignCurveInfo["values"] = usdRates
foreignCurve = parseMktData(foreignCurveInfo)
surf = fxVolatilitySurfaceBuilder(
refDate, ccyPair, quoteNames, quoteTerms, quotes, spot,
domesticCurve, foreignCurve, "SVI")
// ── 3. 图 1 LINE —— 2D 利率曲线 ───────────────────────
//
// plot(data, , title, LINE, stacking, extras)
//
// - data 矩阵,每列一条折线
// - title [标题, X轴, Y轴] ← LINE 用 3 个元素
// - extras { multiYAxes: true } → 两列数据各自独立 Y 轴
// { autoScaleYAxes: true } → 各 Y 轴自动缩放
//
// 跳过近端 4 天 (1d/1w/2w/3w),从 1M 起用 curvePredict 插 100 个点
startDate = curveDates[4]
endDate = curveDates[12]
nPts = 100
span = endDate - startDate
dates100 = array(DATE, nPts)
for (i in 0:nPts) { dates100[i] = startDate + int(i * span / (nPts - 1.0)) }
cnyDense = curvePredict(domesticCurve, dates100) * 100.0
usdDense = curvePredict(foreignCurve, dates100) * 100.0
labels100 = array(STRING, nPts)
for (i in 0:nPts) { labels100[i] = string(dates100[i]) }
mCurves = matrix(cnyDense, usdDense)
mCurves.rename!(labels100, ["CNY (%)", "USD (%)"])
// rename! 设置行标签 → X 轴刻度,列名 → 图例名称
plot(mCurves, ,
["CNY 与 USD 利率曲线", "日期", "利率 (%)"],
LINE, false, { multiYAxes: true, autoScaleYAxes: true })
// ── 4. 提取曲面 & 构建 9-Delta 网格 ─────────────────────
//
// extractMktData(surf) 返回字典:
// volSmiles[i]["strikes"] → 第 i 期限的 5 个 Strike
// volSmiles[i]["vols"] → 对应的 5 个 IV
// termDates → 各期限到期日
//
// 5 个标准 Delta 对应: 10P, 25P, ATM, 25C, 10C
//
// 用 cubicSpline 将 strike 关于 delta 做插值,
// 扩展到 9 点: 10P 20P 30P 40P ATM 40C 30C 20C 10C
// 然后 optionVolPredict 在这些 strike 上查 IV
surfDict = extractMktData(surf)
smiles = surfDict["volSmiles"]
termDates = surfDict["termDates"]
nTenors = size(smiles)
// 跳过近端 4 个期限 (1d/1w/2w/3w),保留 1M~3Y 共 9 个
skipN = 4
nUsed = nTenors - skipN
usedTerms = ["1M","2M","3M","6M","9M","1y","18M","2y","3y"]
usedDates = array(DATE, nUsed)
for (i in 0:nUsed) { usedDates[i] = termDates[i + skipN] }
// delta 坐标: 10→10P, 25→25P, 50→ATM, 75→25C, 90→10C(与 strike 单调递增)
knownDelta = [10.0, 25.0, 50.0, 75.0, 90.0]
targetDelta = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0]
deltaLabels = ["10P","20P","30P","40P","ATM","40C","30C","20C","10C"]
nDelta = 9
deltaIvRows = array(ANY, nUsed)
for (i in 0:nUsed) {
ks = smiles[i + skipN]["strikes"]
// cubicSpline: strike 关于 delta 的插值函数
cs = cubicSpline(knownDelta, ks)
targetStrikes = cubicSplinePredict(cs, targetDelta)
deltaIvRows[i] = flatten(optionVolPredict(surf, usedDates[i], targetStrikes) * 100.0)
}
deltaIvMat = matrix(deltaIvRows).transpose()
deltaIvMat.rename!(usedTerms, deltaLabels)
print("=== 9-Delta IV 矩阵 (%) ===")
print(deltaIvMat)
// ── 5. 图 2 SURFACE —— 3D Strike 曲面 ──────────────────
//
// plot(data, , title, SURFACE)
//
// - data 矩阵。数值 = Z 轴高度
// 行标签 → Y 轴 列标签 → X 轴
// - title [标题, X轴, Y轴, Z轴] ← SURFACE 必须 4 个元素
// - SURFACE 模式不支持 extras
//
// optionVolPredict(surf, dates, strikes) → len(dates)×len(strikes) 矩阵
globalKmin = smiles[skipN]["strikes"][0]
globalKmax = smiles[skipN]["strikes"][4]
for (i in (skipN+1):nTenors) {
ks = smiles[i]["strikes"]
if (ks[0] < globalKmin) { globalKmin = ks[0] }
if (ks[4] > globalKmax) { globalKmax = ks[4] }
}
nK = 21; nT = 30
denseStrikes = globalKmin + (globalKmax - globalKmin) / (nK - 1) * double(0..(nK - 1))
daySpan = usedDates[nUsed - 1] - usedDates[0]
denseDates = array(DATE, nT)
for (i in 0:nT) { denseDates[i] = usedDates[0] + int(i * daySpan / (nT - 1.0)) }
strikeLabels = array(STRING, nK)
for (j in 0:nK) { strikeLabels[j] = string(round(denseStrikes[j], 2)) }
dateLabels = array(STRING, nT)
for (i in 0:nT) { dateLabels[i] = string(denseDates[i]) }
volMat = optionVolPredict(surf, denseDates, denseStrikes) * 100.0
volMat.rename!(dateLabels, strikeLabels)
plot(volMat, ,
["USDCNY 波动率曲面 (Strike, 30×21)", "Strike", "到期日", "隐含波动率 (%)"],
SURFACE)
// ── 6. 图 3 SURFACE —— 3D Delta 曲面 ──────────────────
// deltaIvMat 行=9期限(1M~3Y),列=9 Delta(10P~10C)
plot(deltaIvMat, ,
["USDCNY 波动率曲面 (Delta, 9×9)", "Delta", "期限", "隐含波动率 (%)"],
SURFACE)
// ── 7. 图 4 LINE —— Vol vs Strike 微笑 ────────────────
//
// 选 5 个代表性期限,每条线 = 一个期限的 IV 随 Strike 变化
// transpose: optionVolPredict 返回 5行×21列 → 转为 21行×5列
// 每列一条折线,行标签=Strike 作为 X 轴
pickNames = ["1M","3M","6M","1Y","3Y"]
pickDates = [usedDates[0], usedDates[2], usedDates[3], usedDates[5], usedDates[8]]
strikeMat = optionVolPredict(surf, pickDates, denseStrikes) * 100.0
mVolStrike = strikeMat.transpose()
mVolStrike.rename!(strikeLabels, pickNames)
plot(mVolStrike, ,
["Vol vs Strike 微笑", "Strike", "隐含波动率 (%)"],
LINE, false, { autoScaleYAxes: true })
// ── 8. 图 5 LINE —— Vol vs Delta 微笑 ────────────────
//
// 从 deltaIvMat 选 5 行(期限),拼成新矩阵
// 行标签=deltaLabels 作为 X 轴,列名=pickNames 作图例
pickIdx = [0, 2, 3, 5, 8]
mVolDelta = matrix(
flatten(deltaIvMat[pickIdx[0], ]),
flatten(deltaIvMat[pickIdx[1], ]),
flatten(deltaIvMat[pickIdx[2], ]),
flatten(deltaIvMat[pickIdx[3], ]),
flatten(deltaIvMat[pickIdx[4], ]))
mVolDelta.rename!(deltaLabels, pickNames)
plot(mVolDelta, ,
["Vol vs Delta 微笑", "Delta", "隐含波动率 (%)"],
LINE, false, { autoScaleYAxes: true })