首页 > 编程开发 > python数据分析 >
-
绘制动态K线图:鼠标悬停显示指标、切换周期
第12章 动态K线图实战
12.1 绘制动态K线图:鼠标悬停显示指标、切换周期
12.1.1 先讲个真实需求:老板要我做一个动态K线图
上个月老板让我做一个动态K线图,要求鼠标悬停时显示详细的技术指标,还要能切换不同的时间周期(日线、周线、月线)。我之前只会用Matplotlib画静态图,完全不知道怎么实现这些功能。后来发现Plotly可以轻松实现这些需求,而且不需要前端知识。今天我就把动态K线图的实现方法讲清楚,帮你快速做出专业的动态K线图。
12.1.2 核心技术:Plotly的交互功能与多周期数据切换
-
鼠标悬停显示指标
Plotly支持自定义悬停提示内容,可以通过hovertemplate参数设置鼠标悬停时显示的详细信息,包括开盘价、收盘价、最高价、最低价、成交量、MACD、KDJ、RSI等技术指标。 -
切换周期
Plotly支持通过下拉菜单切换不同的时间周期数据,比如日线、周线、月线。可以通过updatemenus参数实现下拉菜单,当用户选择不同的周期时,自动更新图表的数据。
12.1.3 实战1:用Plotly实现鼠标悬停显示指标
-
实战代码:鼠标悬停显示详细指标
python
# 1. 导入需要的库
import tushare as ts
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
# 2. 获取贵州茅台的日线数据
pro = ts.pro_api()
df_daily = pro.daily(ts_code='600519.SH', start_date='20250101', end_date='20250710')
# 3. 数据预处理
df_daily['trade_date'] = pd.to_datetime(df_daily['trade_date'], format='%Y%m%d')
df_daily.sort_values('trade_date', inplace=True)
# 4. 计算技术指标
# 计算均线
df_daily['ma5'] = df_daily['close'].rolling(window=5).mean()
df_daily['ma20'] = df_daily['close'].rolling(window=20).mean()
df_daily['ma60'] = df_daily['close'].rolling(window=60).mean()
# 计算MACD
ema12 = df_daily['close'].ewm(span=12, adjust=False).mean()
ema26 = df_daily['close'].ewm(span=26, adjust=False).mean()
df_daily['dif'] = ema12 - ema26
df_daily['dea'] = df_daily['dif'].ewm(span=9, adjust=False).mean()
df_daily['macd'] = 2 * (df_daily['dif'] - df_daily['dea'])
# 计算KDJ
low9 = df_daily['low'].rolling(window=9).min()
high9 = df_daily['high'].rolling(window=9).max()
df_daily['rsv'] = (df_daily['close'] - low9) / (high9 - low9) * 100
df_daily['k'] = np.nan
df_daily['d'] = np.nan
df_daily.loc[df_daily.index[8], 'k'] = df_daily.loc[df_daily.index[8], 'rsv']
df_daily.loc[df_daily.index[8], 'd'] = df_daily.loc[df_daily.index[8], 'rsv']
for i in range(9, len(df_daily)):
df_daily.loc[df_daily.index[i], 'k'] = (2/3) * df_daily.loc[df_daily.index[i-1], 'k'] + (1/3) * df_daily.loc[df_daily.index[i], 'rsv']
df_daily.loc[df_daily.index[i], 'd'] = (2/3) * df_daily.loc[df_daily.index[i-1], 'd'] + (1/3) * df_daily.loc[df_daily.index[i], 'k']
df_daily['j'] = 3 * df_daily['k'] - 2 * df_daily['d']
# 计算RSI
df_daily['change'] = df_daily['close'].diff()
df_daily['up'] = np.where(df_daily['change'] > 0, df_daily['change'], 0)
df_daily['down'] = np.where(df_daily['change'] < 0, -df_daily['change'], 0)
up6 = df_daily['up'].rolling(window=6).mean()
down6 = df_daily['down'].rolling(window=6).mean()
df_daily['rsi6'] = up6 / (up6 + down6) * 100
# 5. 创建子图
fig = make_subplots(
rows=2, cols=1,
shared_xaxes=True,
vertical_spacing=0.05,
row_heights=[0.7, 0.3]
)
# 6. 添加K线图到第一个子图
fig.add_trace(
go.Candlestick(
x=df_daily['trade_date'],
open=df_daily['open'],
high=df_daily['high'],
low=df_daily['low'],
close=df_daily['close'],
name='K线图',
increasing_line_color='red',
decreasing_line_color='green',
increasing_fillcolor='rgba(255, 0, 0, 0.3)',
decreasing_fillcolor='rgba(0, 255, 0, 0.3)',
# 自定义悬停提示内容
hovertemplate='<b>日期: %{x}</b><br>'
'开盘价: %{open:.2f}元<br>'
'最高价: %{high:.2f}元<br>'
'最低价: %{low:.2f}元<br>'
'收盘价: %{close:.2f}元<br>'
'5日均线: %{customdata[0]:.2f}元<br>'
'20日均线: %{customdata[1]:.2f}元<br>'
'60日均线: %{customdata[2]:.2f}元<br>'
'MACD: %{customdata[3]:.2f}<br>'
'KDJ: K=%{customdata[4]:.2f}, D=%{customdata[5]:.2f}, J=%{customdata[6]:.2f}<br>'
'RSI6: %{customdata[7]:.2f}<br>'
'<extra></extra>', # 隐藏默认的额外信息
# 自定义数据,用于悬停提示
customdata=np.stack([
df_daily['ma5'],
df_daily['ma20'],
df_daily['ma60'],
df_daily['macd'],
df_daily['k'],
df_daily['d'],
df_daily['j'],
df_daily['rsi6']
], axis=-1)
),
row=1, col=1
)
# 7. 添加均线到第一个子图
fig.add_trace(
go.Scatter(
x=df_daily['trade_date'],
y=df_daily['ma5'],
name='5日均线',
line=dict(color='#1f77b4', width=2),
opacity=0.8,
hovertemplate='5日均线: %{y:.2f}元<br><extra></extra>'
),
row=1, col=1
)
fig.add_trace(
go.Scatter(
x=df_daily['trade_date'],
y=df_daily['ma20'],
name='20日均线',
line=dict(color='#ff7f0e', width=2),
opacity=0.8,
hovertemplate='20日均线: %{y:.2f}元<br><extra></extra>'
),
row=1, col=1
)
fig.add_trace(
go.Scatter(
x=df_daily['trade_date'],
y=df_daily['ma60'],
name='60日均线',
line=dict(color='#2ca02c', width=2),
opacity=0.8,
hovertemplate='60日均线: %{y:.2f}元<br><extra></extra>'
),
row=1, col=1
)
# 8. 添加成交量柱形图到第二个子图
fig.add_trace(
go.Bar(
x=df_daily['trade_date'],
y=df_daily['vol'],
name='成交量',
marker_color=np.where(df_daily['close'] >= df_daily['open'], 'red', 'green'),
opacity=0.7,
hovertemplate='成交量: %{y:.0f}手<br><extra></extra>'
),
row=2, col=1
)
# 9. 设置图表布局
fig.update_layout(
title={
'text': '贵州茅台2025年动态K线图(鼠标悬停显示指标)',
'y': 0.95,
'x': 0.5,
'xanchor': 'center',
'yanchor': 'top',
'font': dict(size=20, color='#333333')
},
yaxis_title='价格(元)',
yaxis2_title='成交量(手)',
xaxis_title='日期',
height=800,
width=1200,
template='plotly_white',
hovermode='x unified',
legend={
'x': 0.02,
'y': 0.98,
'bgcolor': 'rgba(255, 255, 255, 0.8)',
'bordercolor': '#333333',
'borderwidth': 1,
'font': dict(size=10)
},
margin=dict(l=50, r=50, t=80, b=50)
)
# 10. 设置X轴和Y轴的样式
fig.update_xaxes(
rangeslider_visible=False,
tickformat='%Y-%m-%d',
tickangle=45
)
fig.update_yaxes(
gridcolor='#cccccc',
gridwidth=1,
griddash='--'
)
# 11. 保存网页
fig.write_html('贵州茅台2025年动态K线图_鼠标悬停显示指标.html')
# 12. 显示图表
fig.show()
逐行讲解:
hovertemplate:自定义悬停提示的内容,使用%{x}、%{open}、%{high}等变量vb.net教程C#教程python教程SQL教程access 2010教程获取K线图的数据,使用%{customdata[0]}等变量获取自定义数据
customdata:自定义数据,用于悬停提示,需要把多个技术指标的数据堆叠成一个二维数组,每个元素对应一个K线的技术指标数据
np.stack([...], axis=-1):把多个技术指标的数据堆叠成一个二维数组,axis=-1表示按列堆叠
运行结果:
会在浏览器中打开一个网页,显示贵州茅台2025年的动态K线图,鼠标悬停时会显示详细的技术指标,包括开盘价、收盘价、最高价、最低价、成交量、5日均线、20日均线、60日均线、MACD、KDJ、RSI等。
12.1.4 实战2:用Plotly实现切换周期的动态K线图
-
实战代码:切换周期的动态K线图
python
# 1. 导入需要的库
import tushare as ts
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
# 2. 获取贵州茅台的多周期数据
pro = ts.pro_api()
# 获取日线数据
df_daily = pro.daily(ts_code='600519.SH', start_date='20250101', end_date='20250710')
# 获取周线数据
df_weekly = pro.weekly(ts_code='600519.SH', start_date='20250101', end_date='20250710')
# 获取月线数据
df_monthly = pro.monthly(ts_code='600519.SH', start_date='20250101', end_date='20250710')
# 3. 数据预处理
# 处理日线数据
df_daily['trade_date'] = pd.to_datetime(df_daily['trade_date'], format='%Y%m%d')
df_daily.sort_values('trade_date', inplace=True)
df_daily['ma5'] = df_daily['close'].rolling(window=5).mean()
df_daily['ma20'] = df_daily['close'].rolling(window=20).mean()
df_daily['ma60'] = df_daily['close'].rolling(window=60).mean()
# 处理周线数据
df_weekly['trade_date'] = pd.to_datetime(df_weekly['trade_date'], format='%Y%m%d')
df_weekly.sort_values('trade_date', inplace=True)
df_weekly['ma5'] = df_weekly['close'].rolling(window=5).mean()
df_weekly['ma20'] = df_weekly['close'].rolling(window=20).mean()
# 处理月线数据
df_monthly['trade_date'] = pd.to_datetime(df_monthly['trade_date'], format='%Y%m%d')
df_monthly.sort_values('trade_date', inplace=True)
df_monthly['ma5'] = df_monthly['close'].rolling(window=5).mean()
# 4. 创建子图
fig = make_subplots(
rows=2, cols=1,
shared_xaxes=True,
vertical_spacing=0.05,
row_heights=[0.7, 0.3]
)
# 5. 添加初始数据(日线)到子图
# 添加K线图
fig.add_trace(
go.Candlestick(
x=df_daily['trade_date'],
open=df_daily['open'],
high=df_daily['high'],
low=df_daily['low'],
close=df_daily['close'],
name='K线图',
increasing_line_color='red',
decreasing_line_color='green',
increasing_fillcolor='rgba(255, 0, 0, 0.3)',
decreasing_fillcolor='rgba(0, 255, 0, 0.3)',
hovertemplate='<b>日期: %{x}</b><br>'
'开盘价: %{open:.2f}元<br>'
'最高价: %{high:.2f}元<br>'
'最低价: %{low:.2f}元<br>'
'收盘价: %{close:.2f}元<br>'
'5日均线: %{customdata[0]:.2f}元<br>'
'20日均线: %{customdata[1]:.2f}元<br>'
'60日均线: %{customdata[2]:.2f}元<br>'
'<extra></extra>',
customdata=np.stack([
df_daily['ma5'],
df_daily['ma20'],
df_daily['ma60']
], axis=-1)
),
row=1, col=1
)
# 添加均线
fig.add_trace(
go.Scatter(
x=df_daily['trade_date'],
y=df_daily['ma5'],
name='5日均线',
line=dict(color='#1f77b4', width=2),
opacity=0.8,
hovertemplate='5日均线: %{y:.2f}元<br><extra></extra>'
),
row=1, col=1
)
fig.add_trace(
go.Scatter(
x=df_daily['trade_date'],
y=df_daily['ma20'],
name='20日均线',
line=dict(color='#ff7f0e', width=2),
opacity=0.8,
hovertemplate='20日均线: %{y:.2f}元<br><extra></extra>'
),
row=1, col=1
)
fig.add_trace(
go.Scatter(
x=df_daily['trade_date'],
y=df_daily['ma60'],
name='60日均线',
line=dict(color='#2ca02c', width=2),
opacity=0.8,
hovertemplate='60日均线: %{y:.2f}元<br><extra></extra>'
),
row=1, col=1
)
# 添加成交量
fig.add_trace(
go.Bar(
x=df_daily['trade_date'],
y=df_daily['vol'],
name='成交量',
marker_color=np.where(df_daily['close'] >= df_daily['open'], 'red', 'green'),
opacity=0.7,
hovertemplate='成交量: %{y:.0f}手<br><extra></extra>'
),
row=2, col=1
)
# 6. 创建切换周期的下拉菜单
updatemenus = [
dict(
buttons=list([
# 切换到日线
dict(
label='日线',
method='update',
args=[
# 更新数据
{
'x': [df_daily['trade_date'], df_daily['trade_date'], df_daily['trade_date'], df_daily['trade_date'], df_daily['trade_date']],
'open': [df_daily['open'], None, None, None, None],
'high': [df_daily['high'], None, None, None, None],
'low': [df_daily['low'], None, None, None, None],
'close': [df_daily['close'], None, None, None, None],
'y': [None, df_daily['ma5'], df_daily['ma20'], df_daily['ma60'], df_daily['vol']],
'customdata': [np.stack([df_daily['ma5'], df_daily['ma20'], df_daily['ma60']], axis=-1), None, None, None, None]
},
# 更新布局
{
'title': '贵州茅台2025年动态K线图(日线)',
'yaxis_title': '价格(元)',
'yaxis2_title': '成交量(手)'
}
]
),
# 切换到周线
dict(
label='周线',
method='update',
args=[
# 更新数据
{
'x': [df_weekly['trade_date'], df_weekly['trade_date'], df_weekly['trade_date'], df_weekly['trade_date'], df_weekly['trade_date']],
'open': [df_weekly['open'], None, None, None, None],
'high': [df_weekly['high'], None, None, None, None],
'low': [df_weekly['low'], None, None, None, None],
'close': [df_weekly['close'], None, None, None, None],
'y': [None, df_weekly['ma5'], df_weekly['ma20'], None, df_weekly['vol']],
'customdata': [np.stack([df_weekly['ma5'], df_weekly['ma20'], np.full(len(df_weekly), np.nan)], axis=-1), None, None, None, None]
},
# 更新布局
{
'title': '贵州茅台2025年动态K线图(周线)',
'yaxis_title': '价格(元)',
'yaxis2_title': '成交量(手)'
}
]
),
# 切换到月线
dict(
label='月线',
method='update',
args=[
# 更新数据
{
'x': [df_monthly['trade_date'], df_monthly['trade_date'], df_monthly['trade_date'], df_monthly['trade_date'], df_monthly['trade_date']],
'open': [df_monthly['open'], None, None, None, None],
'high': [df_monthly['high'], None, None, None, None],
'low': [df_monthly['low'], None, None, None, None],
'close': [df_monthly['close'], None, None, None, None],
'y': [None, df_monthly['ma5'], None, None, df_monthly['vol']],
'customdata': [np.stack([df_monthly['ma5'], np.full(len(df_monthly), np.nan), np.full(len(df_monthly), np.nan)], axis=-1), None, None, None, None]
},
# 更新布局
{
'title': '贵州茅台2025年动态K线图(月线)',
'yaxis_title': '价格(元)',
'yaxis2_title': '成交量(手)'
}
]
)
]),
direction='down', # 下拉菜单方向
showactive=True, # 显示当前选中的按钮
active=0, # 默认选中第一个按钮(日线)
x=0.02, # 下拉菜单水平位置
y=1.05, # 下拉菜单垂直位置
bgcolor='rgba(255, 255, 255, 0.8)', # 下拉菜单背景颜色
bordercolor='#333333', # 下拉菜单边框颜色
borderwidth=1 # 下拉菜单边框宽度
)
]
# 7. 设置图表布局
fig.update_layout(
title={
'text': '贵州茅台2025年动态K线图(日线)',
'y': 0.95,
'x': 0.5,
'xanchor': 'center',
'yanchor': 'top',
'font': dict(size=20, color='#333333')
},
yaxis_title='价格(元)',
yaxis2_title='成交量(手)',
xaxis_title='日期',
height=800,
width=1200,
template='plotly_white',
hovermode='x unified',
legend={
'x': 0.02,
'y': 0.98,
'bgcolor': 'rgba(255, 255, 255, 0.8)',
'bordercolor': '#333333',
'borderwidth': 1,
'font': dict(size=10)
},
margin=dict(l=50, r=50, t=80, b=50),
updatemenus=updatemenus # 添加下拉菜单
)
# 8. 设置X轴和Y轴的样式
fig.update_xaxes(
rangeslider_visible=False,
tickformat='%Y-%m-%d',
tickangle=45
)
fig.update_yaxes(
gridcolor='#cccccc',
gridwidth=1,
griddash='--'
)
# 9. 保存网页
fig.write_html('贵州茅台2025年动态K线图_切换周期.html')
# 10. 显示图表
fig.show()
逐行讲解:
pro.weekly(...):获取周线数据,tushare的weekly接口返回的是每周的K线数据
pro.monthly(...):获取月线数据,tushare的monthly接口返回的是每月的K线数据
updatemenus:创建切换周期的下拉菜单,每个按钮对应一个周期的数据更新
method='update':更新图表的数据和布局
args:更新的参数,第一个元素是数据更新,第二个元素是布局更新
'x': [df_daily['trade_date'], ...]:更新每个trace的X轴数据,trace的顺序和添加的顺序一致
'y': [None, df_daily['ma5'], ...]:更新每个trace的Y轴数据,None表示不更新该trace的Y轴数据
active=0:默认选中第一个按钮(日线)
运行结果:
会在浏览器中打开一个网页,显示贵州茅台2025年的动态K线图,点击下拉菜单可以切换不同的时间周期(日线、周线、月线),鼠标悬停时会显示详细的技术指标。
12.1.5 基础知识拓展:动态K线图的高级功能
-
实时更新数据
可以通过plotly.express或dash实现实时更新数据,比如每隔5分钟更新一次K线图数据:
python
import dash
from dash import dcc, html, Input, Output
import plotly.graph_objects as go
import pandas as pd
import tushare as ts
app = dash.Dash(__name__)
app.layout = html.Div([
dcc.Interval(
id='interval-component',
interval=5*60*1000, # 每隔5分钟更新一次
n_intervals=0
),
dcc.Graph(id='live-k线图')
])
@app.callback(
Output('live-k线图', 'figure'),
Input('interval-component', 'n_intervals')
)
def update_graph(n):
pro = ts.pro_api()
df = pro.daily(ts_code='600519.SH', start_date='20250101', end_date='20250710')
df['trade_date'] = pd.to_datetime(df['trade_date'], format='%Y%m%d')
df.sort_values('trade_date', inplace=True)
fig = go.Figure(data=[go.Candlestick(x=df['trade_date'], open=df['open'], high=df['high'], low=df['low'], close=df['close'])])
return fig
if __name__ == '__main__':
app.run_server(debug=True)
-
添加技术指标切换
可以通过下拉菜单切换不同的技术指标,比如MACD、KDJ、RSI等:
python
updatemenus = [
dict(
buttons=list([
dict(
label='MACD',
method='update',
args=[
{'y': [None, None, None, df_daily['dif'], df_daily['dea'], df_daily['macd']]},
{'yaxis_title': 'MACD'}
]
),
dict(
label='KDJ',
method='update',
args=[
{'y': [None, None, None, df_daily['k'], df_daily['d'], df_daily['j']]},
{'yaxis_title': 'KDJ'}
]
)
])
)
]
12.1.6 总结:动态K线图的应用场景
1.实时监控:用动态K线图实时监控股票的走势和技术指标,当指标达到预设条件时自动执行交易
2.数据分析:通过切换不同的时间周期,分析股票的长期趋势和短期波动
3.教学演示:在教学中使用动态K线图,可以更直观地展示股票的走势和技术指标的变化,提高教学效果
4.投资决策:投资者可以通过动态K线图判断股票的买卖时机,提高投资收益
通过这个实战,你应该已经掌握了用Plotly实现动态K线图的方法,包括鼠标悬停显示指标、切换周期等功能。接下来可以尝试用这些方法制作更专业的股票分析工具和实时监控系统。
下一节咱们就讲如何用Python做量化策略回测,帮你验证策略的有效性。
本站原创,转载请注明出处:https://www.xin3721.com/ArticlePrograme/csharp49708.html










