import os
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from trade_utils import get_daily, get_etf_name_map
# 设置渲染器为 Jupyter 默认
import plotly.io as pio
pio.renderers.default = 'plotly_mimetype+notebook'
symbol = '513100.SH'
name_map = get_etf_name_map()
display_name = name_map.get(symbol, symbol)
# 获取日线数据
daily = get_daily([symbol])
# 确保按日期排序
daily = daily.sort_values('date').reset_index(drop=True)
# 只保留本标的(如 get_daily 返回多标的时)
if 'code' in daily.columns:
daily = daily[daily['code'] == symbol].copy()
# 转换日期为 datetime
if not np.issubdtype(daily['date'].dtype, np.datetime64):
daily['date'] = pd.to_datetime(daily['date'])
# 统一列名(lower case assumed)
close = daily['close']
high = daily['high']
low = daily['low']
volume = daily['volume']
1 数据概览与收盘价/成交量
code | date | open | high | low | close | volume | |
---|---|---|---|---|---|---|---|
0 | 513100.SH | 2013-05-14 | 0.990 | 0.999 | 0.989 | 0.997 | 877116 |
1 | 513100.SH | 2013-05-15 | 0.997 | 0.999 | 0.994 | 0.999 | 265570 |
2 | 513100.SH | 2013-05-16 | 0.999 | 1.000 | 0.996 | 0.997 | 36379 |
fig = make_subplots(specs=[[{"secondary_y": True}]])
fig.add_trace(
go.Scatter(x=daily['date'], y=close, name='Close', line=dict(color='#1f77b4')),
secondary_y=False
)
fig.add_trace(
go.Bar(x=daily['date'], y=volume, name='Volume', marker_color='rgba(200,200,200,0.6)') ,
secondary_y=True
)
fig.update_yaxes(title_text='Price', secondary_y=False)
fig.update_yaxes(title_text='Volume', secondary_y=True)
fig.update_layout(title=f'{display_name}({symbol}) 收盘价与成交量')
fig
2 日收益率与累计收益
- 定义
- 日收益率
ret_t = close_t / close_{t-1} - 1
- 对数收益
logret_t = ln(close_t) - ln(close_{t-1})
- 累计收益
cumret_t = Π(1+ret_i) - 1
daily['ret'] = close.pct_change()
daily['logret'] = np.log(close).diff()
daily['cumret'] = (1 + daily['ret']).cumprod() - 1
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05)
fig.add_trace(go.Scatter(x=daily['date'], y=daily['ret'], name='Daily Return', line=dict(color='#9467bd')), row=1, col=1)
fig.add_trace(go.Scatter(x=daily['date'], y=daily['cumret'], name='Cumulative Return', line=dict(color='#2ca02c')), row=2, col=1)
fig.update_layout(title=f'{display_name} 日收益与累计收益')
fig
3 波动率(年化)
- 定义
- 用对数收益标准差近似波动率,年化:
vol_annual = std(logret) * sqrt(252)
- 也可按滚动窗口计算滚动年化波动
np.float64(0.5175767400767213)
4 移动均线(MA)与指数移动均线(EMA)
- 定义
- 简单移动均线:
MA_n = mean(close_{t-n+1..t})
- 指数移动均线:
EMA_n = EMA_{n, t-1} + α(close_t - EMA_{n, t-1})
,α=2/(n+1)
daily['ma20'] = close.rolling(20).mean()
daily['ma60'] = close.rolling(60).mean()
daily['ema12'] = close.ewm(span=12, adjust=False).mean()
daily['ema26'] = close.ewm(span=26, adjust=False).mean()
fig = go.Figure()
fig.add_trace(go.Scatter(x=daily['date'], y=close, name='Close', line=dict(color='#1f77b4')))
fig.add_trace(go.Scatter(x=daily['date'], y=daily['ma20'], name='MA20', line=dict(color='#ff7f0e')))
fig.add_trace(go.Scatter(x=daily['date'], y=daily['ma60'], name='MA60', line=dict(color='#2ca02c')))
fig.update_layout(title=f'{display_name} 收盘价与MA')
fig
5 布林带(Bollinger Bands)
- 定义
- 中轨:
MB = MA_n
- 上轨:
UB = MB + k * std_n
- 下轨:
LB = MB - k * std_n
,通常n=20, k=2
n, k = 20, 2
mid = daily['ma20']
std = close.rolling(n).std()
daily['bb_upper'] = mid + k * std
daily['bb_lower'] = mid - k * std
fig = go.Figure()
fig.add_trace(go.Scatter(x=daily['date'], y=close, name='Close', line=dict(color='#1f77b4')))
fig.add_trace(go.Scatter(x=daily['date'], y=mid, name='MB(20)', line=dict(color='#ff7f0e')))
fig.add_trace(go.Scatter(x=daily['date'], y=daily['bb_upper'], name='UB', line=dict(color='rgba(255,127,14,0.6)')))
fig.add_trace(go.Scatter(x=daily['date'], y=daily['bb_lower'], name='LB', line=dict(color='rgba(255,127,14,0.6)')))
fig.update_layout(title=f'{display_name} 布林带(20,2)')
fig
6 RSI(相对强弱指标)
- 定义(Wilder)
- 设
U_t = max(Δclose_t, 0)
,D_t = max(-Δclose_t, 0)
- 平滑平均:
AU = EMA(U, n)
,AD = EMA(D, n)
,RS = AU/AD
RSI = 100 - 100/(1+RS)
,常用n=14
n = 14
delta = close.diff()
up = np.where(delta > 0, delta, 0.0)
down = np.where(delta < 0, -delta, 0.0)
au = pd.Series(up, index=close.index).ewm(alpha=1/n, adjust=False).mean()
ad = pd.Series(down, index=close.index).ewm(alpha=1/n, adjust=False).mean()
rs = au / (ad.replace(0, np.nan))
daily['rsi14'] = 100 - 100 / (1 + rs)
fig = go.Figure()
fig.add_trace(go.Scatter(x=daily['date'], y=daily['rsi14'], name='RSI14', line=dict(color='#d62728')))
fig.add_hrect(y0=30, y1=70, fillcolor='LightGray', opacity=0.2, line_width=0)
fig.update_layout(title=f'{display_name} RSI(14)')
fig
7 MACD
- 定义
DIF = EMA12 - EMA26
DEA = EMA(DIF, 9)
MACD = 2 * (DIF - DEA)
(有的实现不乘2)
dif = daily['ema12'] - daily['ema26']
dea = dif.ewm(span=9, adjust=False).mean()
macd = 2 * (dif - dea)
daily['dif'], daily['dea'], daily['macd'] = dif, dea, macd
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.03,
row_heights=[0.6, 0.4])
fig.add_trace(go.Scatter(x=daily['date'], y=close, name='Close', line=dict(color='#1f77b4')), row=1, col=1)
fig.add_trace(go.Scatter(x=daily['date'], y=dif, name='DIF', line=dict(color='#ff7f0e')), row=2, col=1)
fig.add_trace(go.Scatter(x=daily['date'], y=dea, name='DEA', line=dict(color='#2ca02c')), row=2, col=1)
fig.add_trace(go.Bar(x=daily['date'], y=macd, name='MACD', marker_color=np.where(macd>=0, '#d62728', '#17becf')), row=2, col=1)
fig.update_layout(title=f'{display_name} MACD 指标')
fig
8 ATR(平均真实波幅)
- 定义
TR_t = max(high_t - low_t, |high_t - close_{t-1}|, |low_t - close_{t-1}|)
ATR_n = MA(TR, n)
,常用n=14
9 成交量均线
- 定义
VMA_n = mean(volume_{t-n+1..t})
daily['vma20'] = volume.rolling(20).mean()
fig = make_subplots(specs=[[{"secondary_y": True}]])
fig.add_trace(go.Bar(x=daily['date'], y=volume, name='Volume', marker_color='rgba(150,150,200,0.5)'), secondary_y=False)
fig.add_trace(go.Scatter(x=daily['date'], y=daily['vma20'], name='VMA20', line=dict(color='#ff7f0e')), secondary_y=False)
fig.update_layout(title=f'{display_name} 成交量与VMA20')
fig
10 结语
- 本笔记要点
- 了解了常见价格与量能类指标的定义与计算
- 用 Plotly 对
513100.SH
进行了可视化 - 指标参数并非固定,可按需要调整、组合与回测