Python绘制BOLL布林线指标图
写在前面
本文代码部分总结自Packt出版社的《Learn Algorithmic Trading - Fundamentals of Algorithmic Trading》(图1)。现将该书的布林线技术指标部分进行总结,并对数据处理及图形绘制等函数做了相应改动,供有需要的读者学习研究。
布林线(BOLL)技术指标简介
布林线(Bollinger Bands,BOLL)又称布林带,是约翰·布林(John Bollinger)提出的一种行情价格频带分轨,是根据统计学中的标准差原理,设计出来的一种非常实用的技术指标。布林线也建立在移动平均线之上,但包含最近的价格波动,使指标更能适应不同的市场条件。布林线通常可由上轨(压力线)、中轨(行情平衡线)和下轨(支撑线)三条轨道线组成,属于通道式指标或路径式指标[1]。
BOLL公式详解
参数设置
n : n:n: 时间周期数
标准差σ \sigmaσ(STDEV
):
σ = ∑ i = 1 n ( P i − M A ) 2 n \sigma=\sqrt\frac{\sum_{i=1}^{n}(P i-MA)^{2}}{n}σ=n∑i=1n(Pi−MA)2
标准差因子β \betaβ(STDEV Factor
):
β = 2 \beta=2β=2
中界线:n nn日内收盘价的算术平均
阻力线:中界线+ ++标准差因子× \times×标准差
支撑线:中界线− -−标准差因子× \times×标准差
B B A N D M i d = M A n − p e r i o d s B B A N D U p = B B A N D M i d + β × σ B B A N D L o w = B B A N D M i d − β × σ
BBANDMid=MAn−periodsBBANDUp=BBANDMid+β×σBBANDLow=BBANDMid−β×σ
用到的主要Python库
Python绘图库Matplotlib 3.2.1
Python金融数据处理库Pandas 1.0.2
Python矩阵计算库Numpy 1.16.0
Python代码&详解
# 导入及处理数据
import pandas as pd
import numpy as np
# 绘图
import matplotlib.pyplot as plt
# 设置图像标签显示中文
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
import matplotlib as mpl
# 解决一些编辑器(VSCode)或IDE(PyCharm)等存在的图片显示问题,
# 应用Tkinter绘图,以便对图形进行放缩操作
mpl.use('TkAgg')
# 导入数据并做处理
def import_csv(stock_code):
df = pd.read_csv(stock_code + '.csv')
df.rename(columns={
'date': 'Date',
'open': 'Open',
'high': 'High',
'low': 'Low',
'close': 'Close',
'volume': 'Volume'
},
inplace=True)
df['Date'] = pd.to_datetime(df['Date'], format='%Y/%m/%d')
df.set_index(['Date'], inplace=True)
return df
stock_code = 'sh600519'
# 绘制数据的规模
scale = 500
df = import_csv(stock_code)[-scale:]
# SMA:简单移动平均(Simple Moving Average)
time_period = 20 # SMA的计算周期,默认为20
stdev_factor = 2 # 上下频带的标准偏差比例因子
history = [] # 每个计算周期所需的价格数据
sma_values = [] # 初始化SMA值
upper_band = [] # 初始化阻力线价格
lower_band = [] # 初始化支撑线价格
# 构造列表形式的绘图数据
for close_price in df['Close']:
#
history.append(close_price)
# 计算移动平均时先确保时间周期不大于20
if len(history) > time_period:
del (history[0])
# 将计算的SMA值存入列表
sma = np.mean(history)
sma_values.append(sma)
# 计算标准差
stdev = np.sqrt(np.sum((history - sma) ** 2) / len(history))
upper_band.append(sma + stdev_factor * stdev)
lower_band.append(sma - stdev_factor * stdev)
# 将计算的数据合并到DataFrame
df = df.assign(收盘价=pd.Series(df['Close'], index=df.index))
df = df.assign(中界线=pd.Series(sma_values, index=df.index))
df = df.assign(阻力线=pd.Series(upper_band, index=df.index))
df = df.assign(支撑线=pd.Series(lower_band, index=df.index))
# 绘图
ax = plt.figure()
# 设定y轴标签
ax.ylabel = '%s price in ¥' % (stock_code)
df['收盘价'].plot(color='k', lw=1., legend=True)
df['中界线'].plot(color='b', lw=1., legend=True)
df['阻力线'].plot(color='r', lw=1., legend=True)
df['支撑线'].plot(color='g', lw=1., legend=True)
plt.show()
所得图像如下:
参考文献
[1] 麻道明.如何看懂技术指标.北京:中国宇航出版社,2015.207页
[1] Sebastien Donadio,Sourav Ghosh.Learn Algorithmic Trading - Fundamentals of Algorithmic Trading.Birmingham:Packt Press,2019.P59-62.
推荐阅读
echarts添加多条辅助线(MarkLine)_echarts minorsplitline_夕降巫咸的博客-CSDN博客
先上效果图
下面贴option代码
-
option = {
-
title: {
-
text: '多条MarkLine实例',
-
left: 'center'
-
},
-
tooltip: {
-
trigger: 'item',
-
formatter: '{a} <br/>{b} : {c}'
-
},
-
legend: {
-
left: 'left',
-
data: ['2的指数', '3的指数']
-
},
-
xAxis: {
-
type: 'category',
-
name: 'x',
-
splitLine: {show: false},
-
data: ['一', '二', '三', '四', '五', '六', '七', '八', '九']
-
},
-
grid: {
-
left: '3%',
-
right: '4%',
-
bottom: '3%',
-
containLabel: true
-
},
-
yAxis: {
-
type: 'log',
-
name: 'y',
-
minorTick: {
-
show: true
-
},
-
minorSplitLine: {
-
show: true
-
},
-
max:'dataMax',
-
min:'dataMin'
-
-
},
-
series: [
-
{
-
name: '3的指数',
-
type: 'line',
-
data: [1, 3, 9, 27, 81, 247, 15, 122, 25]
-
},
-
{
-
name: '2的指数',
-
type: 'line',
-
data: [1, 2, 4, 8, 16, 32, 64, 128, 256]
-
},
-
{
-
name: '1/2的指数',
-
type: 'line',
-
data: [1/2, 1/4, 1/8, 1/16, 1/32, 1/64, 1/128, 1/256, 1/512],
-
-
//实现辅助线部分
-
-
-
markLine: {
-
silent: true,
-
data: [{
-
yAxis: 5
-
}, {
-
yAxis: 10
-
}, {
-
yAxis: 15
-
}, {
-
yAxis: 25
-
}, {
-
yAxis: 35
-
}],
-
lineStyle: {
-
normal: {
-
type: 'solid',
-
},
-
},
-
-
}
-
}
-
]
-
};
pyecharts显示K线、均线、成交量和MACD_yaxis_opts=opts.axisopts(is_scale=true)是什么意思?_lrobinson的博客-CSDN博客
安装 Ta-lib:
pip install Ta-lib
安装pyecharts:
pip install pyecharts
npm install -g phantomjs-prebuilt
安装图片保存插件:
pip install pyecharts-snapshot
-
import pandas as pd
-
import numpy as np
-
import talib as ta
-
from decimal import Decimal
-
-
from pyecharts.charts import Kline, Line, Bar, Grid
-
from pyecharts.commons.utils import JsCode
-
from pyecharts import options as opts
-
from pyecharts.globals import CurrentConfig, NotebookType
-
CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK
-
-
# 精度计算
-
def digital_utils(temps):
-
temps = str(temps)
-
if temps.find('E'):
-
temps = '{:.8f}'.format(Decimal(temps))
-
nums = temps.split('.')
-
if int(nums[1]) == 0:
-
return nums[0]
-
else:
-
num = str(int(nums[1][::-1]))
-
result = '{}.{}'.format(nums[0], num[::-1])
-
return result
-
-
def sum_resampler(df):
-
if df.shape[0] < 1:
-
return float("nan")
-
return np.sum(df)
-
def low_resampler(df):
-
return np.min(df)
-
def high_resampler(df):
-
return np.max(df)
-
def avg_resampler(df):
-
return digital_utils(np.average(df))
-
def open_resampler(df):
-
if df.shape[0] < 1:
-
return float("nan")
-
return np.asarray(df)[0]
-
def close_resampler(df):
-
if df.shape[0] < 1:
-
return float("nan")
-
return np.asarray(df)[-1]
-
def volume_resampler(df):
-
if df.shape[0] < 1:
-
return float("nan")
-
volume = np.asarray(df)[-1] - np.asarray(df)[0]
-
if volume < 1:
-
return float("nan")
-
return volume
-
# 根据tikt数据合成K线数据
-
def getKline(price, volume):
-
data_close = price.resample('T', label='right').apply(close_resampler) # 1分钟聚合,使用最右边的index作为新的index
-
data_open = price.resample('T', label='right').apply(open_resampler) # 1分钟聚合,使用最右边的index作为新的index
-
data_high = price.resample('T', label='right').apply(high_resampler) # 1分钟聚合,使用最右边的index作为新的index
-
data_low = price.resample('T', label='right').apply(low_resampler) # 1分钟聚合,使用最右边的index作为新的index
-
data_volume = volume.resample('T', label='right').apply(volume_resampler) # 1分钟聚合,使用最右边的index作为新的index
-
-
data = pd.concat([data_open, data_close, data_low, data_high, data_volume],axis=1)
-
data.columns = ['open', 'close', 'low', 'high', 'volume']
-
-
return data
-
-
def process(data):
-
#去掉数据中的第一行(集合竞价值)和最后一行数据(结算值)
-
data.columns = ['localtime', 'InstrumentID', 'TradingDay', 'ActionDay', 'UpdateTime', 'UpdateMillisec', 'LastPrice', 'Volume', 'HighestPrice', 'LowestPrice', 'OpenPrice', 'ClosePrice', 'AveragePrice', 'AskPrice1', 'AskVolume1', 'BidPrice1', 'BidVolume1', 'UpperLimitPrice', 'LowerLimitPrice', 'OpenInterest', 'Turnover', 'PreClosePrice', 'PreOpenInterest', 'PreSettlementPrice']
-
data = data[:-1]
-
#用交易日期(TradingDay),会把前一天晚上的数据当成今天数据处理 ActionDay
-
index = pd.DatetimeIndex(data['TradingDay'].map(str) + ' ' + data['UpdateTime'].map(str) + '.' + data['UpdateMillisec'].map(str))
-
data.index = index
-
data = data[['LastPrice', 'Volume', 'HighestPrice', 'LowestPrice', 'OpenPrice', 'AveragePrice', 'AskPrice1', 'AskVolume1', 'BidPrice1', 'BidVolume1', 'UpperLimitPrice', 'LowerLimitPrice', 'OpenInterest', 'PreClosePrice', 'PreOpenInterest', 'PreSettlementPrice']]
-
return data
-
-
# 绘制K线
-
def drawKline(kdata):
-
kline=Kline()
-
kline.add_xaxis(xaxis_data=kdata.index.map(str).tolist())
-
-
kline.add_yaxis(series_name="kline",
-
y_axis=kdata.values.tolist(),
-
itemstyle_opts=opts.ItemStyleOpts( #自定义颜色
-
color="#ec0000",
-
color0="#00da3c",
-
border_color="#8A0000",
-
border_color0="#008F28",)
-
)
-
kline.set_global_opts(
-
title_opts=opts.TitleOpts(title="K线周期图表", pos_left="0"),
-
# yaxis_opts=opts.AxisOpts(is_scale=True,splitarea_opts=opts.SplitAreaOpts(is_show=True, areastyle_opts=opts.AreaStyleOpts(opacity=1)),),
-
yaxis_opts=opts.AxisOpts(is_scale=True, splitline_opts=opts.SplitLineOpts(is_show=True)),
-
# tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="line"),
-
datazoom_opts=[
-
opts.DataZoomOpts(is_show=False, type_="inside", xaxis_index=[0, 0], range_end=100),#xaxis_index=[0, 0]设置第一幅图为内部缩放
-
opts.DataZoomOpts(is_show=True, xaxis_index=[0, 1], pos_top="97%", range_end=100), #xaxis_index=[0, 1]连接第二幅图的axis
-
opts.DataZoomOpts(is_show=False, xaxis_index=[0, 2], range_end=100), #xaxis_index=[0, 2]连接第三幅图的axis
-
],
-
# 三个图的 axis 连在一块
-
# axispointer_opts=opts.AxisPointerOpts(
-
# is_show=True,
-
# link=[{"xAxisIndex": "all"}],
-
# label=opts.LabelOpts(background_color="#777"),
-
# ),
-
)
-
# Ma均线
-
maLine = Line()
-
maLine.add_xaxis(kdata.index.map(str).tolist())
-
maLine.add_yaxis(
-
series_name="MA5",
-
y_axis=kdata['close'].rolling(5).mean(),
-
is_smooth=True,
-
linestyle_opts=opts.LineStyleOpts(opacity=0.5),
-
label_opts=opts.LabelOpts(is_show=False),
-
)
-
maLine.add_yaxis(
-
series_name="MA10",
-
y_axis=kdata['close'].rolling(10).mean(),
-
is_smooth=True,
-
linestyle_opts=opts.LineStyleOpts(opacity=0.5),
-
label_opts=opts.LabelOpts(is_show=False),
-
)
-
maLine.set_global_opts(
-
xaxis_opts=opts.AxisOpts(
-
type_="category",
-
grid_index=1,
-
axislabel_opts=opts.LabelOpts(is_show=False),
-
),
-
yaxis_opts=opts.AxisOpts(
-
grid_index=1,
-
split_number=3,
-
axisline_opts=opts.AxisLineOpts(is_on_zero=False),
-
axistick_opts=opts.AxisTickOpts(is_show=False),
-
splitline_opts=opts.SplitLineOpts(is_show=False),
-
axislabel_opts=opts.LabelOpts(is_show=True),
-
),
-
)
-
# Overlap Kline + ma
-
overlap_kline_ma = kline.overlap(maLine)
-
return overlap_kline_ma
-
-
#绘制成交量图
-
def drawVolume(kdata):
-
volumeFlag = kdata['close'] - kdata['open']
-
barVolume = Bar()
-
barVolume.add_xaxis(kdata.index.map(str).tolist())
-
barVolume.add_yaxis(
-
series_name="Volumn",
-
y_axis=kdata['volume'].values.tolist(),
-
xaxis_index=1,
-
yaxis_index=1,
-
label_opts=opts.LabelOpts(is_show=False),
-
itemstyle_opts=opts.ItemStyleOpts(
-
color=JsCode(
-
"""
-
function(params) {
-
var colorList;
-
if (volumeFlag[params.dataIndex] > 0) {
-
colorList = '#ef232a';
-
} else {
-
colorList = '#14b143';
-
}
-
return colorList;
-
}
-
"""
-
)
-
)
-
)
-
barVolume.set_global_opts(
-
xaxis_opts=opts.AxisOpts(
-
type_="category",
-
grid_index=1,
-
axislabel_opts=opts.LabelOpts(is_show=False),
-
),
-
legend_opts=opts.LegendOpts(is_show=False),
-
)
-
-
return volumeFlag, barVolume
-
-
def drawMACD(kdata):
-
dw = pd.DataFrame()
-
dw['DIF'], dw['DEA'], dw['MACD'] = ta.MACD(kdata['close'], fastperiod=12, slowperiod=26, signalperiod=9)
-
-
bar_2 = Bar()
-
bar_2.add_xaxis(kdata.index.map(str).tolist())
-
-
-
bar_2.add_yaxis(
-
series_name="MACD",
-
y_axis=dw["MACD"].values.tolist(),
-
xaxis_index=1, # 用于合并显示时排列位置,单独显示不要添加
-
yaxis_index=1, # 用于合并显示时排列位置,单独显示不要添加
-
label_opts=opts.LabelOpts(is_show=False),
-
itemstyle_opts=opts.ItemStyleOpts(
-
color=JsCode(
-
"""
-
function(params) {
-
var colorList;
-
if (params.data >= 0) {
-
colorList = '#ef232a';
-
} else {
-
colorList = '#14b143';
-
}
-
return colorList;
-
}
-
"""
-
)
-
),
-
)
-
bar_2.set_global_opts(
-
xaxis_opts=opts.AxisOpts(
-
type_="category",
-
grid_index=1, # 用于合并显示时排列位置,单独显示不要添加
-
axislabel_opts=opts.LabelOpts(is_show=False),
-
),
-
yaxis_opts=opts.AxisOpts(
-
grid_index=1, # 用于合并显示时排列位置,单独显示不要添加
-
split_number=4,
-
axisline_opts=opts.AxisLineOpts(is_on_zero=False),
-
axistick_opts=opts.AxisTickOpts(is_show=False),
-
splitline_opts=opts.SplitLineOpts(is_show=False),
-
axislabel_opts=opts.LabelOpts(is_show=True),
-
),
-
legend_opts=opts.LegendOpts(is_show=False),
-
)
-
line_2 = Line()
-
line_2.add_xaxis(kdata.index.map(str).tolist())
-
line_2.add_yaxis(
-
series_name="DIF",
-
y_axis=dw["DIF"],
-
xaxis_index=1,
-
yaxis_index=2,
-
label_opts=opts.LabelOpts(is_show=False),
-
)
-
line_2.add_yaxis(
-
series_name="DEA",
-
y_axis=dw["DEA"],
-
xaxis_index=1,
-
yaxis_index=2,
-
label_opts=opts.LabelOpts(is_show=False),
-
)
-
line_2.set_global_opts(legend_opts=opts.LegendOpts(is_show=False))
-
overlap_bar_line = bar_2.overlap(line_2)
-
return overlap_bar_line
-
-
def drawAll(overlap_kline_ma, barVolume, overlap_bar_line, volumeFlag):
-
# 最后的 Grid
-
grid_chart = Grid(init_opts=opts.InitOpts(width="1400px", height="800px"))
-
# 这个是为了把 volumeFlag 这个数据写入到 html 中,还没想到怎么跨 series 传值
-
# demo 中的代码也是用全局变量传的
-
grid_chart.add_js_funcs("var volumeFlag = {}".format(volumeFlag.values.tolist()))# 传递涨跌数据给vomume绘图,用红色显示上涨成交量,绿色显示下跌成交量
-
# K线图和 MA5 的折线图
-
grid_chart.add(
-
overlap_kline_ma,
-
grid_opts=opts.GridOpts(pos_left="3%", pos_right="1%", height="60%"),
-
)
-
# Volumn 柱状图
-
grid_chart.add(
-
barVolume,
-
grid_opts=opts.GridOpts(
-
pos_left="3%", pos_right="1%", pos_top="71%", height="10%"
-
),
-
)
-
# MACD DIFS DEAS
-
grid_chart.add(
-
overlap_bar_line,
-
grid_opts=opts.GridOpts(
-
pos_left="3%", pos_right="1%", pos_top="82%", height="14%"
-
),
-
)
-
# grid_chart.render_notebook()
-
return grid_chart
-
-
def getGrid(kdata):
-
k = drawKline(kdata)
-
vflag, v = drawVolume(kdata)
-
m = drawMACD(kdata)
-
return drawAll(k, v, m, vflag)
-
-
def getKdata(path):
-
data = pd.read_csv(path)
-
data = process(data)
-
price = data['LastPrice']
-
volume = data['Volume']
-
kdata = getKline(price, volume)
-
kdata.drop(kdata[np.isnan(kdata['volume'])].index, inplace=True)#删除 volume列中为nan的行
-
return kdata
-
-
def showCsvKline(path):
-
kdata = getKdata(path)
-
return getGrid(kdata)
-
-
path = 'c:\\QiHuoData\\20200204_fu2005.csv'
-
grid_chart =showCsvKline(path)
-
grid_chart.render_notebook()