Python绘制BOLL布林线指标图

写在前面

本文代码部分总结自Packt出版社《Learn Algorithmic Trading - Fundamentals of Algorithmic Trading》(图1)。现将该书的布林线技术指标部分进行总结,并对数据处理及图形绘制等函数做了相应改动,供有需要的读者学习研究。

图1

图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}σ=ni=1n(PiMA)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=MAnperiodsBBANDUp=BBANDMid+β×σBBANDLow=BBANDMidβ×σ��������=���−��������������=��������+�×���������=��������−�×�

BBANDMid=MAnperiodsBBANDUp=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

参考文献

[1] 麻道明.如何看懂技术指标.北京:中国宇航出版社,2015.207页

[1] Sebastien Donadio,Sourav Ghosh.Learn Algorithmic Trading - Fundamentals of Algorithmic Trading.Birmingham:Packt Press,2019.P59-62.

推荐阅读

用mplfinance库绘制股票K线、均线图

Python绘制MACD指标图

Python绘制RSI相对强弱指标图

echarts添加多条辅助线(MarkLine)_echarts minorsplitline_夕降巫咸的博客-CSDN博客

先上效果图

 下面贴option代码

  1. option = {
  2. title: {
  3. text: '多条MarkLine实例',
  4. left: 'center'
  5. },
  6. tooltip: {
  7. trigger: 'item',
  8. formatter: '{a} <br/>{b} : {c}'
  9. },
  10. legend: {
  11. left: 'left',
  12. data: ['2的指数', '3的指数']
  13. },
  14. xAxis: {
  15. type: 'category',
  16. name: 'x',
  17. splitLine: {show: false},
  18. data: ['一', '二', '三', '四', '五', '六', '七', '八', '九']
  19. },
  20. grid: {
  21. left: '3%',
  22. right: '4%',
  23. bottom: '3%',
  24. containLabel: true
  25. },
  26. yAxis: {
  27. type: 'log',
  28. name: 'y',
  29. minorTick: {
  30. show: true
  31. },
  32. minorSplitLine: {
  33. show: true
  34. },
  35. max:'dataMax',
  36. min:'dataMin'
  37.  
  38. },
  39. series: [
  40. {
  41. name: '3的指数',
  42. type: 'line',
  43. data: [1, 3, 9, 27, 81, 247, 15, 122, 25]
  44. },
  45. {
  46. name: '2的指数',
  47. type: 'line',
  48. data: [1, 2, 4, 8, 16, 32, 64, 128, 256]
  49. },
  50. {
  51. name: '1/2的指数',
  52. type: 'line',
  53. data: [1/2, 1/4, 1/8, 1/16, 1/32, 1/64, 1/128, 1/256, 1/512],
  54.  
  55. //实现辅助线部分
  56.  
  57.  
  58. markLine: {
  59. silent: true,
  60. data: [{
  61. yAxis: 5
  62. }, {
  63. yAxis: 10
  64. }, {
  65. yAxis: 15
  66. }, {
  67. yAxis: 25
  68. }, {
  69. yAxis: 35
  70. }],
  71. lineStyle: {
  72. normal: {
  73. type: 'solid',
  74. },
  75. },
  76.  
  77. }
  78. }
  79. ]
  80. };

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
 

 
  1. import pandas as pd
  2. import numpy as np
  3. import talib as ta
  4. from decimal import Decimal
  5.  
  6. from pyecharts.charts import Kline, Line, Bar, Grid
  7. from pyecharts.commons.utils import JsCode
  8. from pyecharts import options as opts
  9. from pyecharts.globals import CurrentConfig, NotebookType
  10. CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK
  11.  
  12. # 精度计算
  13. def digital_utils(temps):
  14. temps = str(temps)
  15. if temps.find('E'):
  16. temps = '{:.8f}'.format(Decimal(temps))
  17. nums = temps.split('.')
  18. if int(nums[1]) == 0:
  19. return nums[0]
  20. else:
  21. num = str(int(nums[1][::-1]))
  22. result = '{}.{}'.format(nums[0], num[::-1])
  23. return result
  24.  
  25. def sum_resampler(df):
  26. if df.shape[0] < 1:
  27. return float("nan")
  28. return np.sum(df)
  29. def low_resampler(df):
  30. return np.min(df)
  31. def high_resampler(df):
  32. return np.max(df)
  33. def avg_resampler(df):
  34. return digital_utils(np.average(df))
  35. def open_resampler(df):
  36. if df.shape[0] < 1:
  37. return float("nan")
  38. return np.asarray(df)[0]
  39. def close_resampler(df):
  40. if df.shape[0] < 1:
  41. return float("nan")
  42. return np.asarray(df)[-1]
  43. def volume_resampler(df):
  44. if df.shape[0] < 1:
  45. return float("nan")
  46. volume = np.asarray(df)[-1] - np.asarray(df)[0]
  47. if volume < 1:
  48. return float("nan")
  49. return volume
  50. # 根据tikt数据合成K线数据
  51. def getKline(price, volume):
  52. data_close = price.resample('T', label='right').apply(close_resampler) # 1分钟聚合,使用最右边的index作为新的index
  53. data_open = price.resample('T', label='right').apply(open_resampler) # 1分钟聚合,使用最右边的index作为新的index
  54. data_high = price.resample('T', label='right').apply(high_resampler) # 1分钟聚合,使用最右边的index作为新的index
  55. data_low = price.resample('T', label='right').apply(low_resampler) # 1分钟聚合,使用最右边的index作为新的index
  56. data_volume = volume.resample('T', label='right').apply(volume_resampler) # 1分钟聚合,使用最右边的index作为新的index
  57.  
  58. data = pd.concat([data_open, data_close, data_low, data_high, data_volume],axis=1)
  59. data.columns = ['open', 'close', 'low', 'high', 'volume']
  60.  
  61. return data
  62.  
  63. def process(data):
  64. #去掉数据中的第一行(集合竞价值)和最后一行数据(结算值)
  65. 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']
  66. data = data[:-1]
  67. #用交易日期(TradingDay),会把前一天晚上的数据当成今天数据处理 ActionDay
  68. index = pd.DatetimeIndex(data['TradingDay'].map(str) + ' ' + data['UpdateTime'].map(str) + '.' + data['UpdateMillisec'].map(str))
  69. data.index = index
  70. data = data[['LastPrice', 'Volume', 'HighestPrice', 'LowestPrice', 'OpenPrice', 'AveragePrice', 'AskPrice1', 'AskVolume1', 'BidPrice1', 'BidVolume1', 'UpperLimitPrice', 'LowerLimitPrice', 'OpenInterest', 'PreClosePrice', 'PreOpenInterest', 'PreSettlementPrice']]
  71. return data
  72.  
  73. # 绘制K线
  74. def drawKline(kdata):
  75. kline=Kline()
  76. kline.add_xaxis(xaxis_data=kdata.index.map(str).tolist())
  77.  
  78. kline.add_yaxis(series_name="kline",
  79. y_axis=kdata.values.tolist(),
  80. itemstyle_opts=opts.ItemStyleOpts( #自定义颜色
  81. color="#ec0000",
  82. color0="#00da3c",
  83. border_color="#8A0000",
  84. border_color0="#008F28",)
  85. )
  86. kline.set_global_opts(
  87. title_opts=opts.TitleOpts(title="K线周期图表", pos_left="0"),
  88. # yaxis_opts=opts.AxisOpts(is_scale=True,splitarea_opts=opts.SplitAreaOpts(is_show=True, areastyle_opts=opts.AreaStyleOpts(opacity=1)),),
  89. yaxis_opts=opts.AxisOpts(is_scale=True, splitline_opts=opts.SplitLineOpts(is_show=True)),
  90. # tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="line"),
  91. datazoom_opts=[
  92. opts.DataZoomOpts(is_show=False, type_="inside", xaxis_index=[0, 0], range_end=100),#xaxis_index=[0, 0]设置第一幅图为内部缩放
  93. opts.DataZoomOpts(is_show=True, xaxis_index=[0, 1], pos_top="97%", range_end=100), #xaxis_index=[0, 1]连接第二幅图的axis
  94. opts.DataZoomOpts(is_show=False, xaxis_index=[0, 2], range_end=100), #xaxis_index=[0, 2]连接第三幅图的axis
  95. ],
  96. # 三个图的 axis 连在一块
  97. # axispointer_opts=opts.AxisPointerOpts(
  98. # is_show=True,
  99. # link=[{"xAxisIndex": "all"}],
  100. # label=opts.LabelOpts(background_color="#777"),
  101. # ),
  102. )
  103. # Ma均线
  104. maLine = Line()
  105. maLine.add_xaxis(kdata.index.map(str).tolist())
  106. maLine.add_yaxis(
  107. series_name="MA5",
  108. y_axis=kdata['close'].rolling(5).mean(),
  109. is_smooth=True,
  110. linestyle_opts=opts.LineStyleOpts(opacity=0.5),
  111. label_opts=opts.LabelOpts(is_show=False),
  112. )
  113. maLine.add_yaxis(
  114. series_name="MA10",
  115. y_axis=kdata['close'].rolling(10).mean(),
  116. is_smooth=True,
  117. linestyle_opts=opts.LineStyleOpts(opacity=0.5),
  118. label_opts=opts.LabelOpts(is_show=False),
  119. )
  120. maLine.set_global_opts(
  121. xaxis_opts=opts.AxisOpts(
  122. type_="category",
  123. grid_index=1,
  124. axislabel_opts=opts.LabelOpts(is_show=False),
  125. ),
  126. yaxis_opts=opts.AxisOpts(
  127. grid_index=1,
  128. split_number=3,
  129. axisline_opts=opts.AxisLineOpts(is_on_zero=False),
  130. axistick_opts=opts.AxisTickOpts(is_show=False),
  131. splitline_opts=opts.SplitLineOpts(is_show=False),
  132. axislabel_opts=opts.LabelOpts(is_show=True),
  133. ),
  134. )
  135. # Overlap Kline + ma
  136. overlap_kline_ma = kline.overlap(maLine)
  137. return overlap_kline_ma
  138.  
  139. #绘制成交量图
  140. def drawVolume(kdata):
  141. volumeFlag = kdata['close'] - kdata['open']
  142. barVolume = Bar()
  143. barVolume.add_xaxis(kdata.index.map(str).tolist())
  144. barVolume.add_yaxis(
  145. series_name="Volumn",
  146. y_axis=kdata['volume'].values.tolist(),
  147. xaxis_index=1,
  148. yaxis_index=1,
  149. label_opts=opts.LabelOpts(is_show=False),
  150. itemstyle_opts=opts.ItemStyleOpts(
  151. color=JsCode(
  152. """
  153. function(params) {
  154. var colorList;
  155. if (volumeFlag[params.dataIndex] > 0) {
  156. colorList = '#ef232a';
  157. } else {
  158. colorList = '#14b143';
  159. }
  160. return colorList;
  161. }
  162. """
  163. )
  164. )
  165. )
  166. barVolume.set_global_opts(
  167. xaxis_opts=opts.AxisOpts(
  168. type_="category",
  169. grid_index=1,
  170. axislabel_opts=opts.LabelOpts(is_show=False),
  171. ),
  172. legend_opts=opts.LegendOpts(is_show=False),
  173. )
  174.  
  175. return volumeFlag, barVolume
  176.  
  177. def drawMACD(kdata):
  178. dw = pd.DataFrame()
  179. dw['DIF'], dw['DEA'], dw['MACD'] = ta.MACD(kdata['close'], fastperiod=12, slowperiod=26, signalperiod=9)
  180.  
  181. bar_2 = Bar()
  182. bar_2.add_xaxis(kdata.index.map(str).tolist())
  183.  
  184.  
  185. bar_2.add_yaxis(
  186. series_name="MACD",
  187. y_axis=dw["MACD"].values.tolist(),
  188. xaxis_index=1, # 用于合并显示时排列位置,单独显示不要添加
  189. yaxis_index=1, # 用于合并显示时排列位置,单独显示不要添加
  190. label_opts=opts.LabelOpts(is_show=False),
  191. itemstyle_opts=opts.ItemStyleOpts(
  192. color=JsCode(
  193. """
  194. function(params) {
  195. var colorList;
  196. if (params.data >= 0) {
  197. colorList = '#ef232a';
  198. } else {
  199. colorList = '#14b143';
  200. }
  201. return colorList;
  202. }
  203. """
  204. )
  205. ),
  206. )
  207. bar_2.set_global_opts(
  208. xaxis_opts=opts.AxisOpts(
  209. type_="category",
  210. grid_index=1, # 用于合并显示时排列位置,单独显示不要添加
  211. axislabel_opts=opts.LabelOpts(is_show=False),
  212. ),
  213. yaxis_opts=opts.AxisOpts(
  214. grid_index=1, # 用于合并显示时排列位置,单独显示不要添加
  215. split_number=4,
  216. axisline_opts=opts.AxisLineOpts(is_on_zero=False),
  217. axistick_opts=opts.AxisTickOpts(is_show=False),
  218. splitline_opts=opts.SplitLineOpts(is_show=False),
  219. axislabel_opts=opts.LabelOpts(is_show=True),
  220. ),
  221. legend_opts=opts.LegendOpts(is_show=False),
  222. )
  223. line_2 = Line()
  224. line_2.add_xaxis(kdata.index.map(str).tolist())
  225. line_2.add_yaxis(
  226. series_name="DIF",
  227. y_axis=dw["DIF"],
  228. xaxis_index=1,
  229. yaxis_index=2,
  230. label_opts=opts.LabelOpts(is_show=False),
  231. )
  232. line_2.add_yaxis(
  233. series_name="DEA",
  234. y_axis=dw["DEA"],
  235. xaxis_index=1,
  236. yaxis_index=2,
  237. label_opts=opts.LabelOpts(is_show=False),
  238. )
  239. line_2.set_global_opts(legend_opts=opts.LegendOpts(is_show=False))
  240. overlap_bar_line = bar_2.overlap(line_2)
  241. return overlap_bar_line
  242.  
  243. def drawAll(overlap_kline_ma, barVolume, overlap_bar_line, volumeFlag):
  244. # 最后的 Grid
  245. grid_chart = Grid(init_opts=opts.InitOpts(width="1400px", height="800px"))
  246. # 这个是为了把 volumeFlag 这个数据写入到 html 中,还没想到怎么跨 series 传值
  247. # demo 中的代码也是用全局变量传的
  248. grid_chart.add_js_funcs("var volumeFlag = {}".format(volumeFlag.values.tolist()))# 传递涨跌数据给vomume绘图,用红色显示上涨成交量,绿色显示下跌成交量
  249. # K线图和 MA5 的折线图
  250. grid_chart.add(
  251. overlap_kline_ma,
  252. grid_opts=opts.GridOpts(pos_left="3%", pos_right="1%", height="60%"),
  253. )
  254. # Volumn 柱状图
  255. grid_chart.add(
  256. barVolume,
  257. grid_opts=opts.GridOpts(
  258. pos_left="3%", pos_right="1%", pos_top="71%", height="10%"
  259. ),
  260. )
  261. # MACD DIFS DEAS
  262. grid_chart.add(
  263. overlap_bar_line,
  264. grid_opts=opts.GridOpts(
  265. pos_left="3%", pos_right="1%", pos_top="82%", height="14%"
  266. ),
  267. )
  268. # grid_chart.render_notebook()
  269. return grid_chart
  270.  
  271. def getGrid(kdata):
  272. k = drawKline(kdata)
  273. vflag, v = drawVolume(kdata)
  274. m = drawMACD(kdata)
  275. return drawAll(k, v, m, vflag)
  276.  
  277. def getKdata(path):
  278. data = pd.read_csv(path)
  279. data = process(data)
  280. price = data['LastPrice']
  281. volume = data['Volume']
  282. kdata = getKline(price, volume)
  283. kdata.drop(kdata[np.isnan(kdata['volume'])].index, inplace=True)#删除 volume列中为nan的行
  284. return kdata
  285.  
  286. def showCsvKline(path):
  287. kdata = getKdata(path)
  288. return getGrid(kdata)
  289.  
  290. path = 'c:\\QiHuoData\\20200204_fu2005.csv'
  291. grid_chart =showCsvKline(path)
  292. grid_chart.render_notebook()

先上效果图

kline1.gif
[图片上传中...(kline3.gif-3cd3ef-1542639059861-0)]
kline3.gif

源码和使用说明已经开源至GitHub,欢迎各位能提出宝贵的意见噢 https://github.com/2557606319/H5-Kline

 

 

posted @ 2023-07-06 10:25  CharyGao  阅读(508)  评论(0编辑  收藏  举报