金融数据分析师必看:DolphinDB 绘图函数全解析,5个案例学会可视化

0 阅读12分钟

外汇期权、波动率微笑、期限结构……这些复杂的金融数据,如何直观呈现?今天我们用 DolphinDB 内置的 plot() 函数,以外汇期权波动率曲面为例,手把手教你画出专业级的 2D/3D 图表,而且支持鼠标拖拽、缩放、悬停查看数值!

一、plot() 函数核心参数解析

DolphinDB 的 plot() 是一个轻量级但功能强大的绘图函数,无需安装任何第三方库,直接在数据库内完成数据到图形的转换。

plot(data, [labels], title, chartType, [stacking], [extras])

参数详解

参数类型说明
data矩阵列 = 一条线(LINE)或一列刻度(SURFACE);行 = X 轴方向的数据点。
labelsSTRING 向量X 轴刻度文字(填入则覆盖矩阵的原行标签),长度须等于行数。
titleSTRING 向量若标题为标量,则为图标标题,若为矢量,则元素依次为[“标题“,”x轴”,”y轴”,”z轴”]
chartType常量图表类型:LINE / BAR / PIE / AREA / SCATTER / HISTOGRAM / SURFACE
stackingBOOL是否堆叠显示,默认 false。目前仅对 BAR / AREA 有效
extrasDICT附加选项,用 {} 字面量传入。仅支持 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

10P20P30P40PATM40C30C20C10C
1M1.851.791.751.721.691.671.641.601.55
3M3.833.403.233.163.143.153.183.253.38
1Y4.454.224.063.973.954.034.224.534.95
3Y4.884.614.424.334.484.684.965.325.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 调用模式,只需换 datatitle 即可切换维度视角。

示例 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 自带
金融原生矩阵直接来自因子表、波动率曲面对象,无缝衔接
交互式 3DSURFACE 图表支持鼠标拖拽旋转/缩放,适合探索分析
多 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 })