返回

Python K线图:用Pandas Resample和Bokeh实现数据可视化

python

用 Pandas Resample 和 Bokeh 绘制 K 线图:从时间序列数据到可视化

搞金融数据分析或者量化交易,经常需要画 K 线图 (Candlestick Chart)。这种图能直观地展示一个时间段内的开盘价 (Open)、最高价 (High)、最低价 (Low) 和收盘价 (Close),也就是常说的 OHLC 数据。

手里有现成的 OHLC 数据当然好办,用 Bokeh 这类可视化库画起来很顺手。比如下面这段代码,数据已经是整理好的每日 OHLC:

import pandas as pd
from datetime import datetime
from bokeh.plotting import figure, show
from bokeh.models import NumeralTickFormatter

# 准备好每日 OHLC 数据
data_ohlc = {
    'time': pd.to_datetime([ # 确保是 DatetimeIndex 或类似兼容格式
        datetime(2025, 1, 1),
        datetime(2025, 1, 2),
        datetime(2025, 1, 3),
    ]),
    'open':  [10, 40, 20],
    'close': [40, 20, 30],
    'low':   [ 5, 10, 20],
    'high':  [40, 50, 35],
}

df_ohlc = pd.DataFrame(data_ohlc).set_index('time') # 将 time 列设为索引

# 判断涨跌
inc = df_ohlc.close > df_ohlc.open
dec = df_ohlc.open > df_ohlc.close

# 创建 Bokeh 图表
p = figure(x_axis_type="datetime", height=300, width=800, title="每日 K 线图 (OHLC 数据)")
p.xaxis.axis_label = "日期"
p.yaxis.axis_label = "价格"
p.yaxis[0].formatter = NumeralTickFormatter(format="0,0") # 格式化 Y 轴刻度

# 绘制影线 (最高价到最低价的竖线)
# 注意:Bokeh K线图通常需要处理好时间轴,直接用 index 可能对不上位置
# 我们需要一个数值化的时间表示,或者确保 Bokeh 理解 DatetimeIndex
# 为了精确,我们稍微调整一下 x 轴坐标,让 K 线柱体居中显示
# 定义 K 线柱体的宽度,例如半天
w = 12*60*60*1000 # K 线柱体宽度 (ms),半天
p.segment(df_ohlc.index, df_ohlc.high, df_ohlc.index, df_ohlc.low, color="black")

# 绘制实体 (开盘价和收盘价组成的矩形)
# 上涨 K 线 (阳线)
p.vbar(df_ohlc.index[inc], w, df_ohlc.open[inc], df_ohlc.close[inc], fill_color="#49a3a3", line_color="#49a3a3")
# 下跌 K 线 (阴线)
p.vbar(df_ohlc.index[dec], w, df_ohlc.open[dec], df_ohlc.close[dec], fill_color="#eb3c40", line_color="#eb3c40")


show(p)

这段代码跑出来,效果杠杠的,就是一张标准的 K 线图:

candlestick chart

但实际情况往往没这么理想。咱们拿到的原始数据可能更“裸”一点,没有直接的 OHLC,只有一连串的时间戳和对应的价格点,像这样:

import pandas as pd
from datetime import datetime

# 原始数据:时间戳和价格
data_raw = {
    'time': [
        datetime(2025, 1, 1, 6),
        datetime(2025, 1, 1, 10),
        datetime(2025, 1, 1, 14),
        datetime(2025, 1, 1, 18),

        datetime(2025, 1, 2, 6),
        datetime(2025, 1, 2, 10),
        datetime(2025, 1, 2, 14),
        datetime(2025, 1, 2, 18),

        datetime(2025, 1, 3, 6),
        datetime(2025, 1, 3, 10),
        datetime(2025, 1, 3, 14),
        datetime(2025, 1, 3, 18),
    ],
    'price': [
        10,  5, 40, 40, # 1月1日
        40, 10, 50, 20, # 1月2日
        20, 20, 35, 30, # 1月3日
    ]
}
df_raw = pd.DataFrame(data_raw)
# 最好先确认时间列是 datetime 类型
df_raw['time'] = pd.to_datetime(df_raw['time'])
print("原始数据 DataFrame:")
print(df_raw)

面对这种只有时间和价格的数据,想画 K 线图,第一反应就是用 pandas 的 resample 方法。这个方法是处理时间序列数据的神器,能按指定的时间频率(比如每天 'D')对数据进行分组聚合。

咱们知道,一根 K 线需要四个值:开盘价、最高价、最低价、收盘价。对于按天 ('D') 重采样来说:

  • 开盘价 (Open):是当天第一条记录的价格。
  • 最高价 (High):是当天所有价格中的最大值。
  • 最低价 (Low):是当天所有价格中的最小值。
  • 收盘价 (Close):是当天最后一条记录的价格。

Pandas resample 配合聚合函数正好能干这事儿:

# 确保 time 列是 Datetime 类型,并设为索引,这对于 resample 很重要
df_raw = df_raw.set_index('time')

# 按天 ('D') 对数据进行重采样
# 'on=' 参数适用于 DataFrame 列,如果 time 已经是索引,则不需要 on 参数
# kind='period' 会生成 PeriodIndex,而 kind='timestamp' (默认或省略) 生成 DatetimeIndex
# 对于 Bokeh 绘图,DatetimeIndex 通常更方便
resampler = df_raw['price'].resample('D') # 或者 df_raw.resample('D')['price'] 效果类似

# 获取 OHLC 值
open_price = resampler.first()
high_price = resampler.max()
low_price = resampler.min()
close_price = resampler.last()

print("\nResample 后的 OHLC 数据:")
print("Open:\n", open_price)
print("High:\n", high_price)
print("Low:\n", low_price)
print("Close:\n", close_price)

看起来万事俱备,拿到了 open, high, low, close 四个 Series,数据都有了。但问题来了,怎么把这些分散的 Series 喂给 Bokeh 画图呢?原来的代码里,画影线用了 df.index, df.high, df.low,画 K 线实体用了 df.index[inc], df.open[inc] 等等。现在这些 open_price, high_price... 都是独立的 Series,之前的 df.index 该用哪个 Series 的 index 呢?它们按理说 index 应该是一样的(都是按天聚合后的时间戳),但怎么方便地整合起来,生成和第一个例子里 df_ohlc 结构类似的数据框,再传给 Bokeh 呢?

这就是卡壳的地方:如何将 resample 分别计算出的 OHLC 数据组合成一个适合绘图的 DataFrame,并确定绘图时使用的正确时间索引?

为啥直接用 resample 的结果画图会卡壳?

咱们刚才分别调用了 .first(), .max(), .min(), .last(),确实得到了需要的 OHLC 数据。但它们是四个独立的 pandas Series。而 Bokeh 画 K 线图(特别是用 vbarsegment 组合的方式)通常期望你提供一个统一的数据源,比如一个 DataFrame,里面包含了所有绘图需要的信息(时间戳、OHLC 值)。

直接用 open_price.index, high_price, low_price 去画影线,再用 open_price.index[dec], open_price[dec], close_price[dec] 画阴线... 代码会变得非常冗长且混乱。更重要的是,你需要确保这四个 Series 的索引完全一致,并且在筛选 incdec 时,索引也能对齐。虽然 resample 出来的结果索引理论上是一致的,但代码组织上不方便。

核心问题在于,我们需要一个 单一的、包含所有 OHLC 列和对应时间索引的 DataFrame ,就像第一个例子里的 df_ohlc 那样。

解决方案:整合 Resample 结果并绘制 K 线图

解决这个问题的关键,就是把分散的聚合操作合并,一次性生成包含所有 OHLC 信息的 DataFrame。

方案:使用 agg() 方法聚合数据

Pandas resample 对象提供了一个非常强大的方法:agg()。它允许你对同一份重采样后的数据应用多个聚合函数。我们可以把 first, max, min, last 这些操作一次性传给 agg()

1. 原理与作用:

resampler.agg() 接收一个字典或列表,指定要应用的聚合函数。如果对同一个列(比如 'price')应用多个函数,可以传入一个函数名列表。agg() 会返回一个新的 DataFrame,其索引是重采样后的时间戳,列是聚合操作的结果。

2. 代码示例:

import pandas as pd
from datetime import datetime
from bokeh.plotting import figure, show
from bokeh.models import NumeralTickFormatter

# 原始数据
data_raw = {
    'time': [
        datetime(2025, 1, 1, 6), datetime(2025, 1, 1, 10), datetime(2025, 1, 1, 14), datetime(2025, 1, 1, 18),
        datetime(2025, 1, 2, 6), datetime(2025, 1, 2, 10), datetime(2025, 1, 2, 14), datetime(2025, 1, 2, 18),
        datetime(2025, 1, 3, 6), datetime(2025, 1, 3, 10), datetime(2025, 1, 3, 14), datetime(2025, 1, 3, 18),
    ],
    'price': [
        10,  5, 40, 40, # 1140, 10, 50, 20, # 1220, 20, 35, 30, # 13日
    ]
}
df_raw = pd.DataFrame(data_raw)
df_raw['time'] = pd.to_datetime(df_raw['time'])
df_raw = df_raw.set_index('time')

# 使用 agg 一次性计算 OHLC
# 对 'price' 列应用多个聚合函数
ohlc_agg = df_raw['price'].resample('D').agg(['first', 'max', 'min', 'last'])

# 重命名列名,使其更清晰,符合 OHLC 习惯
ohlc_agg.columns = ['open', 'high', 'low', 'close']

print("使用 agg() 生成的 OHLC DataFrame:")
print(ohlc_agg)

# 现在 ohlc_agg 的结构就和第一个例子的 df_ohlc 非常相似了
# 它的索引 ohlc_agg.index 就是我们需要的时间轴
# 它的列包含了 open, high, low, close

# 判断涨跌
inc = ohlc_agg.close > ohlc_agg.open
dec = ohlc_agg.open > ohlc_agg.close

# --- 开始 Bokeh 绘图 ---
p = figure(x_axis_type="datetime", height=350, width=800, title="每日 K 线图 (Resample + agg)")
p.xaxis.axis_label = "日期"
p.yaxis.axis_label = "价格"
p.yaxis.formatter = NumeralTickFormatter(format="0,0.00") # Y轴格式,可根据需要调整

# K 线宽度,根据重采样频率调整。这里是按天 ('D'),可以设为半天宽度
# 注意:如果你的数据时间跨度很大,这个宽度可能需要调整,或者使用 Bokeh 的自动宽度
w = 12 * 60 * 60 * 1000 # 半天的毫秒数

# 绘制影线 (最高价到最低价)
p.segment(ohlc_agg.index, ohlc_agg.high, ohlc_agg.index, ohlc_agg.low, color="black")

# 绘制 K 线实体
# 上涨 K 线 (阳线),填充绿色,边框也是绿色
p.vbar(ohlc_agg.index[inc], w, ohlc_agg.open[inc], ohlc_agg.close[inc], fill_color="#26a69a", line_color="#26a69a")

# 下跌 K 线 (阴线),填充红色,边框也是红色
p.vbar(ohlc_agg.index[dec], w, ohlc_agg.open[dec], ohlc_agg.close[dec], fill_color="#ef5350", line_color="#ef5350")

# 添加交互提示工具 (HoverTool)
from bokeh.models import HoverTool
hover = HoverTool(
    tooltips=[
        ("时间", "@x{%F}"), # 显示日期,格式为 YYYY-MM-DD
        ("开盘", "@open{0,0.00}"),
        ("最高", "@high{0,0.00}"),
        ("最低", "@low{0,0.00}"),
        ("收盘", "@close{0,0.00}"),
    ],
    formatters={
        '@x': 'datetime', # 关键:告诉 HoverTool x 轴是时间格式
        # '@open': 'printf', # 或者用 numbro 格式化
        # '@high': 'printf',
        # '@low': 'printf',
        # '@close': 'printf',
    },
    # 仅在 K 线实体上显示 tooltips
    # 注意:由于我们有两个 vbar glyph(上涨和下跌),需要为它们指定名称
    # 在 vbar 调用时添加 name 参数:name="kline"
    # renderers=[r_inc, r_dec] # 需要获取 vbar 的 renderer 对象
    # 或者,为了简单起见,让它在所有垂直元素上触发
    mode='vline' # 鼠标悬停在垂直区域时触发
)
# 在 p.vbar(...) 调用时添加 name='kline_inc' 和 name='kline_dec'
# ... 然后修改 hover tool 的 renderers ...
# 为了演示方便,这里使用 mode='vline'
p.add_tools(hover)

show(p)

解释:

  1. df_raw['price'].resample('D') 创建了一个按天重采样的 Resampler 对象,作用于 'price' 列。
  2. .agg(['first', 'max', 'min', 'last']) 对每个分组(每天)计算这四个聚合值。结果是一个 DataFrame,索引是每天的开始时间戳,列名是 'first', 'max', 'min', 'last'。
  3. ohlc_agg.columns = ['open', 'high', 'low', 'close'] 把列名改成我们熟悉的 OHLC。
  4. 关键点: 现在 ohlc_agg 这个 DataFrame 的结构和我们最初的目标 df_ohlc 非常相似了!它有一个时间索引 ohlc_agg.index,以及 open, high, low, close 四个列。
  5. 回答核心问题: 绘制 K 线图时,ohlc_agg.index 就是我们要找的等价于原来 df.index 的时间轴 。所有绘图操作(segment, vbar)都应该使用 ohlc_agg.index 作为 X 坐标,并从 ohlc_agg 的相应列中获取 OHLC 数据。
  6. 后续的绘图代码就和第一个例子非常类似了,只是把 df_ohlc 换成了 ohlc_agg

3. 关于时间索引 (PeriodIndex vs DatetimeIndex):

  • 如果你在 resample 时用了 kind='period',生成的索引会是 PeriodIndex。虽然 Bokeh 某些情况下也能处理,但有时可能会遇到兼容性问题或绘图行为不符合预期。
  • resample 默认(或显式使用 kind='timestamp')生成的是 DatetimeIndex,这通常与 Bokeh 的 x_axis_type="datetime" 配合得最好。
  • 如果你的 ohlc_agg.indexPeriodIndex,可以很容易地转换它:
    # 如果 ohlc_agg.index 是 PeriodIndex,转换为 Timestamp Index
    if isinstance(ohlc_agg.index, pd.PeriodIndex):
        ohlc_agg.index = ohlc_agg.index.to_timestamp()
    
    在绘图前做这一步转换,可以确保与 Bokeh 的时间轴类型匹配。上面的 agg 示例代码默认会生成 DatetimeIndex,通常无需转换。

4. 安全建议 / 进阶使用:

  • 数据完整性: 原始数据可能有缺失。resample 对空的时间段默认如何处理(比如是否填充 NaN)取决于你的 pandas 版本和具体用法。检查 ohlc_agg 是否有 NaN 值,可能需要 .dropna() 或者填充处理 (.fillna()),依据你的业务逻辑决定。例如,如果某天完全没有交易数据,OHLC 都应该是 NaN。
  • 真正的开盘/收盘: first()last() 是基于原始数据的时间排序。如果你的数据不是严格按时间排序的,或者一天内可能有多个相同时间戳的数据点,结果可能不符合预期。确保原始数据在 resample 前是按时间排序的 (df_raw.sort_index(inplace=True)).
  • 交易时间: resample('D') 是按自然天聚合。金融市场有交易时段。如果你的数据横跨多个交易日,但你只想聚合每个交易日内部的数据,可能需要更复杂的逻辑(比如先按日期分组,再在组内找特定时间的开盘价等),或者使用特定于交易日历的库。
  • 性能考量: 对于非常大的数据集 (几百万行以上),resample().agg() 仍然是相当高效的。如果遇到性能瓶颈,可以考虑 Dask (用于并行计算 Pandas DataFrame) 等工具。

代码实战:整合后的完整流程

下面是把数据加载、resampleagg 结合、以及用 Bokeh 绘图的完整可运行代码:

import pandas as pd
from datetime import datetime
from bokeh.plotting import figure, show
from bokeh.models import NumeralTickFormatter, HoverTool

# 1. 准备原始数据
data_raw = {
    'time': [
        datetime(2025, 1, 1, 6), datetime(2025, 1, 1, 10), datetime(2025, 1, 1, 14), datetime(2025, 1, 1, 18),
        datetime(2025, 1, 2, 6), datetime(2025, 1, 2, 10), datetime(2025, 1, 2, 14), datetime(2025, 1, 2, 18),
        datetime(2025, 1, 3, 6), datetime(2025, 1, 3, 10), datetime(2025, 1, 3, 14), datetime(2025, 1, 3, 18),
    ],
    'price': [
        10,  5, 40, 40, # 1月1日: open=10, high=40, low=5, close=40 -> 涨
        40, 10, 50, 20, # 1月2日: open=40, high=50, low=10, close=20 -> 跌
        20, 20, 35, 30, # 1月3日: open=20, high=35, low=20, close=30 -> 涨
    ]
}
df_raw = pd.DataFrame(data_raw)
df_raw['time'] = pd.to_datetime(df_raw['time'])
df_raw = df_raw.set_index('time')
# (可选) 确保数据按时间排序,对 first()/last() 很重要
df_raw.sort_index(inplace=True)

# 2. 使用 resample 和 agg 生成 OHLC 数据
# 按天 ('D') 重采样 'price' 列
resampler = df_raw['price'].resample('D')
# 应用聚合函数,一次性得到 OHLC
ohlc_agg = resampler.agg(['first', 'max', 'min', 'last'])
# 重命名列名
ohlc_agg.columns = ['open', 'high', 'low', 'close']

# (可选) 检查并处理缺失值,如果某天无数据,resample 可能产生 NaN 行
# ohlc_agg.dropna(inplace=True) # 如果需要,移除完全没有数据的行

print("生成的 OHLC DataFrame:")
print(ohlc_agg)

# 3. 准备绘图所需数据
# 判断涨跌
inc = ohlc_agg.close > ohlc_agg.open
dec = ohlc_agg.open > ohlc_agg.close
# K线宽度(毫秒) - 半天
w = 12 * 60 * 60 * 1000

# 4. 使用 Bokeh 绘图
p = figure(x_axis_type="datetime", height=400, width=900, title="股票 K 线图 (Pandas Resample 生成)",
           tools="pan,wheel_zoom,box_zoom,reset,save") # 添加常用工具栏
p.xaxis.axis_label = "日期"
p.yaxis.axis_label = "价格"
p.yaxis.formatter = NumeralTickFormatter(format="0,0.00") # Y 轴数字格式
p.grid.grid_line_alpha=0.3 # 网格线透明度

# 绘制影线 (最高/最低价连线)
p.segment(ohlc_agg.index, ohlc_agg.high, ohlc_agg.index, ohlc_agg.low, color="black")

# 绘制 K 线实体 - 阳线 (上涨)
# 添加 name 参数以便 HoverTool 可以指定渲染器
r_inc = p.vbar(ohlc_agg.index[inc], w, ohlc_agg.open[inc], ohlc_agg.close[inc],
       fill_color="#26a69a", line_color="#26a69a", name="kline_inc")

# 绘制 K 线实体 - 阴线 (下跌)
r_dec = p.vbar(ohlc_agg.index[dec], w, ohlc_agg.open[dec], ohlc_agg.close[dec],
       fill_color="#ef5350", line_color="#ef5350", name="kline_dec")

# 添加提示工具 HoverTool
hover = HoverTool(
    # 指定只在这两个 K 线实体渲染器上触发 tooltips
    renderers=[r_inc, r_dec],
    tooltips=[
        ("时间", "@x{%F}"),  # @x 代表 x 轴坐标 (这里是index), {%F} 是日期格式
        ("开盘", "@top{0,0.00}"),  # 对于 vbar, top/bottom 对应开盘/收盘 (或反过来)
        ("收盘", "@bottom{0,0.00}"), # 注意:需要根据 inc/dec 判断哪个是开盘/收盘
                                      # 更通用的方法是直接引用 DataFrame 列
        # 为了通用性,引用原始 ohlc_agg 数据。但这需要将 ohlc_agg 提供给 ColumnDataSource
        # 为了简单起见,这里我们暂时接受 vbar 的 top/bottom (可能需要根据 inc/dec 逻辑反转)
        # --- 或者 ---
        # 使用更可靠的方式,先创建一个 ColumnDataSource
        # from bokeh.models import ColumnDataSource
        # source = ColumnDataSource(ohlc_agg)
        # 然后 p.segment(x='index', y0='low', y1='high', source=source, ...)
        # p.vbar(x='index', top='open', bottom='close', source=source, ...)
        # hover.tooltips = [...] 直接引用列名 '@open', '@close' etc.

        # 修正 tooltip, 使其更准确反映 OHLC (需要一点技巧)
        # Bokeh 的 vbar 对于正负方向 (top/bottom) 有点 tricky
        # 我们还是用比较通用的方式,直接展示对应时间点的 OHLC
        ("开", "@{open}{0,0.00}"), # 需要确保这些列在 CDS 中
        ("高", "@{high}{0,0.00}"),
        ("低", "@{low}{0,0.00}"),
        ("收", "@{close}{0,0.00}"),
    ],
    formatters={'@x': 'datetime', '@{open}': 'printf', '@{high}': 'printf', '@{low}': 'printf', '@{close}': 'printf'},
    mode='vline' # 在垂直线上移动时显示
)
# 如果直接在 p.vbar 上引用 ohlc_agg 的列作为 tooltips,需要确保数据被传递了。
# 最好的方式是创建 ColumnDataSource
from bokeh.models import ColumnDataSource
source = ColumnDataSource(ohlc_agg)
source.add(inc, name='inc') # 把 inc/dec 也加进去,虽然 tooltips 不直接用
source.add(dec, name='dec')

# 重新绘制,使用 ColumnDataSource
p = figure(x_axis_type="datetime", height=400, width=900, title="股票 K 线图 (Pandas Resample 生成 + CDS)",
           tools="pan,wheel_zoom,box_zoom,reset,save,hover") # 直接在 figure 上添加 hover tool
p.xaxis.axis_label = "日期"
p.yaxis.axis_label = "价格"
p.yaxis.formatter = NumeralTickFormatter(format="0,0.00")
p.grid.grid_line_alpha=0.3

p.segment(x='index', y0='low', y1='high', source=source, color="black", name="segment")
p.vbar(x='index', width=w, top='open', bottom='close', source=source, filter=BooleanFilter(booleans=source.data['inc']),
       fill_color="#26a69a", line_color="#26a69a", name="kline_inc")
p.vbar(x='index', width=w, top='open', bottom='close', source=source, filter=BooleanFilter(booleans=source.data['dec']),
       fill_color="#ef5350", line_color="#ef5350", name="kline_dec")


# 获取 HoverTool 并配置 Tooltips (假设 figure 初始化时已添加 hover)
hover_tool = p.select(type=HoverTool)[0]
hover_tool.tooltips=[
        ("时间", "@index{%F}"), # @index 是 CDS 中索引列的默认名称
        ("开", "@open{0,0.00}"),
        ("高", "@high{0,0.00}"),
        ("低", "@low{0,0.00}"),
        ("收", "@close{0,0.00}"),
    ]
hover_tool.formatters={'@index': 'datetime'}
hover_tool.mode='vline'


# # 修正 HoverTool, 使用CDS后,引用列名即可
# # p = figure(... , tools=[... , hover]) # 初始化 figure 时加入 hover
# p.hover.tooltips = [
#     ("时间", "@index{%F}"), # index 是 DatetimeIndex,需告知 formatter
#     ("开盘", "@open{0,0.00}"),
#     ("最高", "@high{0,0.00}"),
#     ("最低", "@low{0,0.00}"),
#     ("收盘", "@close{0,0.00}"),
# ]
# p.hover.formatters = {'@index': 'datetime'} # 告诉 hovertool index 列是时间格式
# p.hover.mode = 'vline' # 鼠标悬停在垂直区域时触发

show(p)

注意: 在最终的代码示例中,为了让 HoverTool 更准确地显示 OHLC 数据,推荐使用了 Bokeh 的 ColumnDataSource。这是一种更标准的做法,它将你的 DataFrame 包装成 Bokeh 能更好理解和引用的数据结构。这样,在定义 tooltips 时,你可以直接使用 DataFrame 的列名(如 @open, @high 等)。@index 通常用来引用数据源的索引。同时,BooleanFilter 用于根据 incdec 条件分别绘制上涨和下跌的 K 线柱。

进阶技巧与注意事项

  • 添加交易量 (Volume): 如果你的原始数据包含交易量 volume 列,可以在 agg 中一起聚合它,通常是求和 sum

    # 假设 df_raw 还有 'volume'
    ohlcv_agg = df_raw.resample('D').agg({
        'price': ['first', 'max', 'min', 'last'],
        'volume': 'sum'
    })
    # 重命名列,可能需要处理多级索引
    ohlcv_agg.columns = ['open', 'high', 'low', 'close', 'volume']
    

    然后可以用 p.vbar 在 K 线图下方添加一个交易量柱状图子图(通常需要创建第二个 figure 并用 gridplotcolumn 布局组合)。

  • 选择重采样频率: 'D' 代表自然日。你可以改成 'B' (工作日), 'W' (周), 'M' (月), 'H' (小时), '15T' (15分钟) 等等,根据你的分析需求调整频率。K 线柱体的宽度 w 也应相应调整。

  • 自定义 K 线颜色: 你可以根据自己的喜好修改阳线 (上涨) 和阴线 (下跌) 的颜色代码。

  • 处理数据间隙: 如果数据在时间上不连续(比如周末或假期没有数据),resample 默认会创建这些时间点的索引,但对应的 OHLC 值可能是 NaN。使用 .dropna() 可以去除这些没有数据的行。如果希望在图上跳过这些间隙,Bokeh 通常会自动处理(因为它基于时间戳绘制)。

现在,你应该能熟练地将只有时间戳和价格的原始数据,通过 pandas.resample().agg() 转换成 OHLC 格式,并用 Bokeh 绘制出清晰、交互式的 K 线图了。