Bokeh 股票相关性仪表盘实战:深入解析 stocks 示例与 Bokeh Server 应用开发

发布时间:2026/11/9 10:11:54

Bokeh 股票相关性仪表盘实战:深入解析 stocks 示例与 Bokeh Server 应用开发 Bokeh 股票相关性仪表盘实战深入解析 stocks 示例与 Bokeh Server 应用开发【免费下载链接】bokehInteractive Data Visualization in the browser, from Python项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh本文以 Bokeh 官方仓库中的stocks示例应用examples/server/app/stocks为主线完整讲解如何用 Bokeh Server 从零构建一个可交互的股票对比仪表盘包括启动命令、目录型应用的目录结构与加载机制、基于yfinance的行情数据加载、联动绘图与选择交互的实现以及app_hooks.py生命周期钩子的底层原理。读完本文你将掌握 Bokeh 服务器应用Bokeh Application的标准开发范式并可以直接复用这套“数据准备 回调联动 布局组合”的模式搭建自己的数据分析看板。一、stocks 示例是做什么的stocks是 Bokeh 仓库中一个典型的Bokeh Server 应用Bokeh Application用于创建“一个简单的股票相关性仪表盘a simple stocks correlation dashboard”。该应用的定位在其源码 examples/server/app/stocks/main.py 的模块 docstring 中写得很清楚Choose stocks to compare in the drop down widgets, and make selections on the plots to update the summary and histograms accordingly.即用户通过下拉框选择两支要对比的股票应用会在散点图上展示两支股票收益率的相关性用户还可以在图上**框选box select**数据点右侧的摘要统计describe()输出会随选择实时更新。整个应用由以下几部分交互构成两个Select下拉框用于选择股票默认候选池为AAPL、GOOG、INTC、NVDA、MSFT一张收益率相关性散点图corr支持box_select框选两条时间序列曲线图ts1、ts2分别显示两支股票的收盘价支持xbox_select横向框选一个PreText统计面板实时展示所选股票的收盘价与收益率的描述性统计describe()。在 Bokeh 官方的 Server 应用总览文档 examples/server/app/README.md 中stocks 被描述为 “Linked plots, summary statistics, and correlations for market data”即“联动绘图、摘要统计与市场数据相关性”这是学习 Bokeh 联动交互最合适的入门级示例之一。二、运行方式一条命令启动整个看板根据 examples/server/app/stocks/README.md启动该应用的命令非常简单先进入其父目录examples/server/app然后执行bokeh serve --show stocks其中bokeh serve是 Bokeh 自带的服务器命令用于托管 Bokeh 应用stocks是应用目录名即examples/server/app/stocks/目录Bokeh 会将其解析为一个目录型应用--show表示启动成功后自动在浏览器中打开新标签页。启动后浏览器地址为http://localhost:5006/stocks端口5006是 Bokeh Server 的默认端口/stocks是应用目录名对应的 URL 路径。关于bokeh serve的更多用法如指定端口--port、--allow-websocket-origin等可以执行bokeh serve --help查看。注意stocks 应用需要联网加载股票数据见下文app_hooks.py并且依赖第三方库yfinance运行前需要保证环境已安装yfinancepip install yfinance。除了 stocksexamples/server/app目录下还提供了clustering、crossfilter、movies、ohlc、selection_histogram、spectrogram等大量同类 Bokeh Server 应用运行方式完全一致例如bokeh serve --show selection_histogram.py直接指定单脚本文件适合对照学习。三、目录型应用的结构与加载机制stocks 是一个目录型应用Directory-style app其目录内只有三个文件examples/server/app/stocks/ ├── README.md # 应用说明与运行命令 ├── app_hooks.py # 服务端生命周期钩子数据预加载 └── main.py # 应用主体构建文档、绘图与回调这种目录结构是 Bokeh Server 的规范约定。从源码 src/bokeh/application/handlers/directory.py 可以看到DirectoryHandler在初始化时会在目录中查找main.py或main.ipynb作为主应用脚本两者同时存在时优先使用main.py见 directory.py#L132-L142查找目录下的app_hooks.py或server_lifecycle.py作为生命周期钩子模块两者不能同时存在否则会抛出ValueError见 directory.py#L149-L162若存在__init__.py则将其作为 Python 包运行使main.py可以通过包名共享模块级状态。也就是说对 Bokeh 而言一个目录只要有main.py就可以被bokeh serve直接当作应用启动app_hooks.py属于可选的“锦上添花”能力用于在服务端生命周期事件中执行数据准备逻辑。3.1 数据加载钩子app_hooks.pystocks 的 app_hooks.py 是理解“Bokeh 应用如何预加载数据”的最佳范例全文如下import yfinance as yf TICKERS [AAPL, GOOG, INTC, NVDA, MSFT] def on_server_loaded(server_context): print(\nLoading data from Yahoo finance..., end) for ticker in TICKERS: df yf.download(ticker, start2020-09-01, end2022-09-01) df[Returns] df[Close].diff() setattr(server_context, ticker, df) print(DONE!\n)关键点解读on_server_loaded(server_context)是 Bokeh 定义的服务端生命周期回调在服务器首次加载该应用时执行一次不是每次会话都执行。它的作用是把五支股票AAPL、GOOG、INTC、NVDA、MSFT在 2020-09-01 至 2022-09-01 期间的数据一次性下载到内存中并通过setattr(server_context, ticker, df)把每个DataFrame挂到server_context上供main.py直接读取。数据来源是 Yahoo Finance通过yfinance库并且额外计算了一列日收益率Returns Close.diff()收盘价的一阶差分。这种“在on_server_loaded中准备全局共享数据”的模式避免了每个浏览器会话都重复下载同一批数据属于 Bokeh Server 应用的标准优化手段。3.2 生命周期钩子的底层原理app_hooks.py之所以能被识别是因为 Bokeh 在DirectoryHandler中专门查找该文件名并交由ServerLifecycleHandler处理见 directory.py#L150-L160。而ServerLifecycleHandler在 src/bokeh/application/handlers/server_lifecycle.py 中实现它会读取钩子模块的__dict__提取其中定义的on_server_loaded、on_server_unloaded、on_session_created、on_session_destroyed四个可选回调见 server_lifecycle.py#L83-L97并用_check_callback校验其签名on_server_loaded必须接收server_context参数。真正的调用时机在 src/bokeh/application/application.py#L195-L203Application.on_server_loaded(server_context)会按顺序调用每个 handler的on_server_loaded。因此若你的应用有多个 handler例如同时挂载多个目录应用每个 handler 的钩子都会被执行on_server_unloaded在服务器干净退出前执行需要注意服务器常被信号直接杀死该回调在实践中不一定可靠。stocks 示例只用了on_server_loaded一个钩子把“下载五支股票数据”这件事与“构建界面”解耦让main.py保持纯粹的可视化逻辑——这是目录型应用推荐的职责划分。四、应用主体 main.py 逐段解析main.py 是应用的核心它完成“取数据 → 建图 → 挂回调 → 布局 → 提交文档”的完整流程。下面按逻辑分段解读。4.1 全局状态与数据读取from functools import lru_cache from bokeh.layouts import column, row from bokeh.models import ColumnDataSource, PreText, Select from bokeh.plotting import curdoc, figure DEFAULT_TICKERS [AAPL, GOOG, INTC, NVDA, MSFT] SERVER_CONTEXT curdoc().session_context.server_context def nix(val, lst): return [x for x in lst if x ! val] lru_cache def get_data(t1, t2): df1 getattr(SERVER_CONTEXT, t1) df2 getattr(SERVER_CONTEXT, t2) df1.columns [f{col[0]}_{col[1]} if isinstance(col, tuple) and col[1] ! else col[0] for col in df1.columns] df2.columns [f{col[0]}_{col[1]} if isinstance(col, tuple) and col[1] ! else col[0] for col in df2.columns] df1 df1.rename(columns{Close: fClose_{t1}, Returns: fReturns_{t1}}) df2 df2.rename(columns{Close: fClose_{t2}, Returns: fReturns_{t2}}) data df1.join(df2, howouter).dropna() data[t1] data[fClose_{t1}] data[t2] data[fClose_{t2}] data[t1_returns] data[fReturns_{t1}] data[t2_returns] data[fReturns_{t2}] return data这里有三处值得注意的实现细节通过session_context.server_context拿到全局数据SERVER_CONTEXT curdoc().session_context.server_context正是读取app_hooks.py中挂载数据的关键句柄。main.py每次执行都通过getattr(SERVER_CONTEXT, ticker)取回对应股票的DataFrame实现“数据加载一次、多处复用”。扁平化 MultiIndex 列名yfinance下载的数据列名是(字段, 股票代码)形式的元组MultiIndex所以这里把元组列名拼接成字段_代码字符串如Close_AAPL再统一重命名为Close_{t1}、Returns_{t1}等带股票后缀的列最后用join(howouter)按日期外连接、dropna()丢弃缺失行。lru_cache缓存get_data(t1, t2)用最近最少使用缓存缓存计算结果当用户反复切换股票组合时相同组合的 DataFrame 无需重复拼接属于典型的性能优化手段。4.2 两个数据源与三张图source ColumnDataSource(datadict(date[], t1[], t2[], t1_returns[], t2_returns[])) source_static ColumnDataSource(datadict(date[], t1[], t2[], t1_returns[], t2_returns[])) corr figure(width370, height350, min_border_left60, toolspan,wheel_zoom,box_select,reset, active_dragbox_select) corr.scatter(t1_returns, t2_returns, size3, sourcesource, selection_colororange, alpha0.8, nonselection_alpha0.1, selection_alpha0.5) ts1 figure(width900, height200, x_axis_typedatetime, toolspan,wheel_zoom,xbox_select,reset, active_dragxbox_select) ts1.line(Date, t1, sourcesource_static) ts1.scatter(Date, t1, size3, sourcesource, colorNone, selection_colororange) ts2 figure(width900, height200, x_axis_typedatetime, toolspan,wheel_zoom,xbox_select,reset, active_dragxbox_select) ts2.line(Date, t2, sourcesource_static) ts2.scatter(Date, t2, sourcesource, colorNone, selection_colororange) ts2.x_range ts1.x_range这段代码展示了 Bokeh 联动交互的经典设计两个ColumnDataSource分工明确source用于散点层承载选择状态source_static用于线层静态底图。选择交互发生在source上而底图线不参与选择因此框选不会改变线的外观。corr相关性散点图以t1_returns为 x 轴、t2_returns为 y 轴绘制散点active_dragbox_select将默认拖拽手势设为框选通过selection_colororange、selection_alpha0.5、nonselection_alpha0.1区分选中/未选中点这是 Bokeh 基于ColumnDataSource选择状态的典型视觉反馈。ts1/ts2时间序列图x_axis_typedatetime让 x 轴按时间刻度渲染ts2.x_range ts1.x_range将两张图的 x 轴范围共享实现缩放/平移时两张时序图联动——这是 Bokeh 中最常用的跨图联动手段之一。4.3 下拉框与回调联动stats PreText(text, width500) ticker1 Select(valueAAPL, optionsnix(GOOG, DEFAULT_TICKERS)) def ticker1_change(attrname, old, new): ticker2.options nix(new, DEFAULT_TICKERS) update() ticker1.on_change(value, ticker1_change) ticker2 Select(valueGOOG, optionsnix(AAPL, DEFAULT_TICKERS)) def ticker2_change(attrname, old, new): ticker1.options nix(new, DEFAULT_TICKERS) update() ticker2.on_change(value, ticker2_change)两个Select下拉框通过on_change(value, callback)注册回调实现互斥选择当用户在ticker1中选择了一支股票ticker1_change会立刻用nix即“从列表中剔除该项”把该股票从ticker2的候选项中移除保证两支股票不会相同然后调用update()刷新全图。回调签名(attrname, old, new)是 Bokeh 属性回调的固定形式分别表示变化的属性名、旧值、新值。4.4 更新与选择回调def update_stats(data, t1, t2): stats.text str(data[[fClose_{t1}, fClose_{t2}, fReturns_{t1}, fReturns_{t1}]].describe()) def update(selectedNone): t1, t2 ticker1.value, ticker2.value data get_data(t1, t2) source.data data source.selected.indices [] source_static.data data corr.title.text f{t1} returns vs. {t2} returns ts1.title.text, ts2.title.text t1, t2 update_stats(data, t1, t2) def selection_change(attrname, old, new): t1, t2 ticker1.value, ticker2.value data get_data(t1, t2) if selected : source.selected.indices: data data.iloc[selected, :] update_stats(data, t1, t2) source.selected.on_change(indices, selection_change)update()是核心刷新函数切换股票后重新取数、把新数据一次性写入source和source_static、清空旧选择source.selected.indices []、更新三张图的标题并刷新统计文本。注意source.data data是把整个DataFrame直接赋给ColumnDataSourceBokeh 会自动按列名匹配列。selection_change监听source.selected.indices即被框选中的行索引。if selected : source.selected.indices:使用了海象运算符当存在选中项时只对选中行做统计data.iloc[selected, :]实现“框选即看子集统计”。一个可以留意的细节从源码看update_stats中统计列列表的第四项写的是Returns_{t1}而不是Returns_{t2}见 main.py#L78-L79。这看起来像示例源码中的一处笔误重复引用了第一支股票的收益率列但不影响应用整体运行读者在自行复刻时可以改为Returns_{t2}以统计两支股票的收益率。4.5 布局与提交文档update() widgets column(row(ticker1, ticker2), stats) main_row row(corr, widgets) series column(ts1, ts2) curdoc().add_root(column(main_row, series)) curdoc().title Stocks最后一步是布局与文档提交用row/column组合布局上方一行是“散点图 右侧控件两个下拉框和统计面板”下方一列是两张时序图脚本末尾调用curdoc().add_root(...)把布局树挂到当前文档curdoc().title Stocks设置浏览器标签页标题update()在末尾被显式调用一次保证页面首次加载时立即渲染出默认股票AAPL vs GOOG的完整数据。这也解释了 Bokeh 目录型应用的本质main.py每次新会话都会执行一遍向curdoc()提交文档内容而app_hooks.py中的on_server_loaded只执行一次用于准备跨会话共享的数据。五、事件驱动模型小结一次完整的交互链路综合main.py与app_hooks.py一次典型交互用户在ticker1中把股票从 AAPL 换成 NVDA的完整链路如下Bokeh Server 启动DirectoryHandler加载app_hooks.py见 directory.py#L150-L162Application.on_server_loaded触发on_server_loaded(server_context)五支股票数据被下载并挂到server_contextapplication.py#L195-L203浏览器打开http://localhost:5006/stocks新会话执行main.pyget_data(AAPL, GOOG)经lru_cache取数并构建界面用户在下拉框选择 NVDAticker1_change回调触发ticker2.options剔除 NVDA随后update()重新取数此时get_data(NVDA, GOOG)命中缓存则直接返回、刷新三张图与统计用户在散点图上框选数据点source.selected.indices变化触发selection_change统计面板切换为选中子集的describe()输出。整条链路完全由 Bokeh Server 的事件回调机制驱动Python 回调运行在服务器端界面状态的变更通过 WebSocket 同步到浏览器端渲染无需编写任何前端 JavaScript。六、如何在此基础上扩展stocks 是一个极具可扩展性的模板读者可以基于它快速搭建自己的看板更换数据源把 app_hooks.py 中yf.download(...)替换为本地 CSV、数据库或自建接口只要最终在server_context上挂载“含日期、收盘价列的DataFrame”即可增加股票池修改DEFAULT_TICKERSmain.py与TICKERSapp_hooks.py为同一组代码即可扩充候选列表增加统计维度在get_data中追加计算列如滚动均值、涨跌幅并在update_stats的describe()之外补充自定义统计参考同目录示例examples/server/app下的 selection_histogram.py 演示了“选中/未选中点分别绘制直方图”的进阶选择交互ohlc 演示了流式更新streaming API非常适合与 stocks 对照学习。七、总结stocks虽然只是一个“简单的股票相关性仪表盘”但它浓缩了 Bokeh Server 应用开发的全部核心知识点目录型应用的组织方式main.py 可选app_hooks.py、服务端生命周期钩子on_server_loaded与跨会话数据共享server_context、ColumnDataSource驱动的选择状态可视化、on_change属性回调与selected.indices选择回调、跨图联动共享x_range以及row/column布局与curdoc()文档提交。掌握了这份示例你就掌握了用纯 Python 构建交互式数据看板的核心方法——这正是 Bokeh “从 Python 到浏览器交互式可视化”理念的最佳入门教材。【免费下载链接】bokehInteractive Data Visualization in the browser, from Python项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/11/6 7:25:53

Spring框架九大设计模式深度解析与实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/7 16:23:10

基于SpringBoot与深度学习的图书推荐系统实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/7 11:43:30

SpringBoot酒店预约系统开发实战与架构设计

1. 项目概述:SpringBoot酒店预约系统开发实录最近在整理过往项目时,翻出这个基于SpringBoot的酒店预约系统(项目编号03663),觉得其中的技术实现和架构设计值得分享。这个系统完整实现了从房型展示、在线预订到订单管理…

2026/11/9 10:09:53

低压电工证考的是什么 值不值得考 信阳班组负责人亲测避坑指南

低压电工证考的是什么 值不值得考 信阳班组负责人亲测避坑指南 证书过期了不知道怎么复审延期?别急,我来给你讲讲低压电工证到底是啥,值不值得考,以及怎么选培训机构、怎么避免被坑。作为一名在信阳干了5年劳务班组负责人,我见过太多电工因为证书过期…

2026/11/9 10:09:53

电工作业检查制度怎么考?官方报名入口在这里,别再交冤枉钱

电工作业检查制度怎么考?官方报名入口在这里,别再交冤枉钱 怕考不过白交培训费?现在电工作业检查制度越来越严格,不少学员在报名前都心里没底,怕花了钱还拿不到证,更怕考不过被培训机构“放鸽子”。别担心,这篇文章帮你搞清楚电工作业检查制度的报考流…

2026/11/9 10:09:53

女特种工包括哪些工种值不值得考?别再犹豫,看完这篇直接上岗

女特种工包括哪些工种值不值得考?别再犹豫,看完这篇直接上岗 急需特种作业证才能上岗找工作,这事儿不能再拖了。特别是女性特种作业人员,很多岗位对性别没有限制,反而因为身体素质、稳定性等优势,越来越受企业欢迎。不过,很多人还是会纠结:女特种工包…

2026/11/9 10:09:53

阜宁电工证培训机构真实经历告诉你怎么避坑

阜宁电工证培训机构真实经历告诉你怎么避坑 你是不是也在阜宁找电工证培训机构,担心被坑?怕选错机构耽误时间和钱?我之前也走过弯路,现在就给你讲讲我的真实经历,帮你少走点冤枉路。 为什么必须考电工证?别以为它不重要…

2026/11/9 10:04:53

电工证操作视频丢了怎么补办 良心建议告诉你该怎么办

电工证操作视频丢了怎么补办 良心建议告诉你该怎么办 证书过期了不知道怎么复审延期,这事儿可真让不少人头疼。尤其是刚毕业的大学生,像信阳的工程类毕业生,一出校门就面临找工作的压力,而手里那张电工证要是没及时复审,那可就白费了。别急,今天我就用…

2026/11/9 0:37:55

电工证丢失还能补吗?千万别踩坑,丢了证等于失业

电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。…

2026/11/9 0:03:09

建设厅的特种工有哪些含金量高?别交完钱考不过

建设厅的特种工有哪些含金量高?别交完钱考不过 怕考不过白交培训费?别急,先搞清楚建设厅的特种工有哪些含金量高的证,再决定是不是值得投入时间和金钱。 建设厅的特种工证是建筑行业里最基础、也最实用的证书之一,涉及多个作业类别,每种证的 含金量…

2026/11/9 0:03:09

荆门市电工证报名怎么考才靠谱 全国通用证别交冤枉钱

荆门市电工证报名怎么考才靠谱 全国通用证别交冤枉钱 怕考不过白交培训费?荆门市电工证报名前必须知道这些避坑指南。别看网上一堆培训机构广告,真要报个电工证,很多人被坑得一塌糊涂。特别是那些打着“包过”旗号的,最后不是考试不过,就是证不是全国通…

2026/11/9 0:03:09

高空电工作业过程通过率揭秘:怎么选靠谱机构不被坑

高空电工作业过程通过率揭秘:怎么选靠谱机构不被坑 不知道去哪报名怕被中介坑?你不是一个人。现在特种作业证报名渠道五花八门,从线下培训机构到线上代理,真假难辨。尤其是像高空电工作业这类高风险、高门槛的作业类型,证书含金量和考试通过率直接影响你…

2026/11/9 6:30:27

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/7 14:50:12

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑