ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

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

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),仅供参考
返回列表