<本文的原始位置: http://bluegene8210.is-programmer.com/posts/24902.html>
---- 花了 20 个小时左右的时间才从新浪下载完复权日线数据,把复权日线表建起来。这速度也太慢了,还有首次下载网页失败的比例居然这么高,一定有问题,印象中以前不是 这么慢的,下载几千只股票的数据也只有几十个页面会首次下载失败吧。但昨天晚上更新最新数据的时候把下载任务之间的延迟扩大了一些,好像好一些,速度还可 以,而且失败率不高。我开的是 5 个线程,下载页面之间的间隔是 0.2 ~ 0.3 秒。
---- 另外,把那个画 K 线图的脚本贴出来。这个脚本是通过研究 Matplotlib 官网里的示例并且借助 Google,用大概 1 周的时间改出来的。简单介绍一下:
1. 由两个子图(subplot)构成,上面一个显示价格(K 线),下面一个显示成交量。
2. K 线子图可以使用线性坐标或者对数坐标(由 Plot() 函数第三个参数控制)。使用线性坐标的时候,每个单位价格区间所占高度是固定的;使用对数坐标的时候,每个单位涨幅区间(比如 10%)所占高度是固定的。成交量子图的高度总是固定,不论成交量数值大小。
3. 对 X 轴来说,每根 K 线的宽度固定,整个图形的宽度决定于行情的天数。只要把行情数据文件作为参数传递过去就可以,图片尺寸由程序自主计算。
4. 另外,figdpi 这个变量控制图片的分辨率(解析度),可以随意调大调小。上一篇文章里贴的图使用的 dpi 值是 300。另外,X 轴和 Y 轴上的坐标点也是程序自主决定的。
---- 整个脚本还是一个 work-in-progress,目前的局限主要在于使用对数坐标时,Y 轴坐标点的确定。前一篇里所贴的那个图,可以看见价格上限在 20 块左右,如果换一只价格 90 块上下的股票,或者用来画几千点的指数行情,那 Y 轴的坐标点就会太密集。解决办法是根据取值区间来自主选择合适的 Y 轴坐标间距,但是这个目前还没有做。
---- 任何意见或建议都许多欢迎 !
---- <补记>:已经有了大幅改进的版本,在下一篇里。

1 # -*- coding: utf-8 -*- 2 3 import sys 4 import pickle 5 import math 6 import datetime 7 import matplotlib 8 9 matplotlib.use("WXAgg", warn=True) # 这个要紧跟在 import matplotlib 之后,而且必须安装了 wxpython 2.8 才行。 10 11 import matplotlib.pyplot as pyplot 12 import numpy 13 from matplotlib.ticker import FixedLocator, MultipleLocator, LogLocator, FuncFormatter, NullFormatter, LogFormatter 14 15 16 17 def Plot(pfile, figpath, useexpo=True): 18 ''' 19 pfile 指明存放绘图数据的 pickle file,figpath 指定图片需存放的路径 20 ''' 21 22 fileobj= open(name=pfile, mode='rb') 23 pdata= pickle.load(fileobj) 24 fileobj.close() 25 26 # 计算图片的尺寸(单位英寸) 27 # 注意:Python2 里面, "1 / 10" 结果是 0, 必须写成 "1.0 / 10" 才会得到 0.1 28 #================================================================================================================================================== 29 30 length= len(pdata[u'日期']) # 所有数据的长度,就是天数 31 32 highest_price= max(pdata[u'最高']) # 最高价 33 lowest_price= min( [plow for plow in pdata[u'最低'] if plow != None] ) # 最低价 34 35 yhighlim_price= round(highest_price+50, -2) # K线子图 Y 轴最大坐标 36 ylowlim_price= round(lowest_price-50, -2) # K线子图 Y 轴最小坐标 37 38 xfactor= 10.0/230.0 # 一条 K 线的宽度在 X 轴上所占距离(英寸) 39 yfactor= 0.3 # Y 轴上每一个距离单位的长度(英寸),这个单位距离是线性坐标和对数坐标通用的 40 41 if useexpo: # 要使用对数坐标 42 expbase= 1.1 # 底数,取得小一点,比较接近 1。股价 3 元到 4 元之间有大约 3 个单位距离 43 ymulti_price= math.log(yhighlim_price, expbase) - math.log(ylowlim_price, expbase) # 价格在 Y 轴上的 “份数” 44 45 else: 46 ymulti_price= (yhighlim_price - ylowlim_price) / 100 # 价格在 Y 轴上的 “份数” 47 48 ymulti_vol= 3.0 # 成交量部分在 Y 轴所占的 “份数” 49 ymulti_top= 0.2 # 顶部空白区域在 Y 轴所占的 “份数” 50 ymulti_bot= 0.8 # 底部空白区域在 Y 轴所占的 “份数” 51 52 xmulti_left= 10.0 # 左侧空白区域所占的 “份数” 53 xmulti_right= 3.0 # 右侧空白区域所占的 “份数” 54 55 xmulti_all= length + xmulti_left + xmulti_right 56 xlen_fig= xmulti_all * xfactor # 整个 Figure 的宽度 57 ymulti_all= ymulti_price + ymulti_vol + ymulti_top + ymulti_bot 58 ylen_fig= ymulti_all * yfactor # 整个 Figure 的高度 59 60 rect_1= (xmulti_left/xmulti_all, (ymulti_bot+ymulti_vol)/ymulti_all, length/xmulti_all, ymulti_price/ymulti_all) # K线图部分 61 rect_2= (xmulti_left/xmulti_all, ymulti_bot/ymulti_all, length/xmulti_all, ymulti_vol/ymulti_all) # 成交量部分 62 63 # 建立 Figure 对象 64 #================================================================================================================================================== 65 figfacecolor= 'white' 66 figedgecolor= 'black' 67 figdpi= 600 68 figlinewidth= 1.0 69 70 figobj= pyplot.figure(figsize=(xlen_fig, ylen_fig), dpi=figdpi, facecolor=figfacecolor, edgecolor=figedgecolor, linewidth=figlinewidth) # Figure 对象 71 72 #================================================================================================================================================== 73 #================================================================================================================================================== 74 #======= 成交量部分 75 #================================================================================================================================================== 76 #================================================================================================================================================== 77 78 # 添加 Axes 对象 79 #================================================================================================================================================== 80 axes_2= figobj.add_axes(rect_2, axis_bgcolor='black') 81 axes_2.set_axisbelow(True) # 网格线放在底层 82 83 # 改变坐标线的颜色 84 #================================================================================================================================================== 85 for child in axes_2.get_children(): 86 if isinstance(child, matplotlib.spines.Spine): 87 child.set_color('lightblue') 88 89 # 得到 X 轴 和 Y 轴 的两个 Axis 对象 90 #================================================================================================================================================== 91 xaxis_2= axes_2.get_xaxis() 92 yaxis_2= axes_2.get_yaxis() 93 94 # 设置两个坐标轴上的 grid 95 #================================================================================================================================================== 96 xaxis_2.grid(True, 'major', color='0.3', linestyle='solid', linewidth=0.2) 97 xaxis_2.grid(True, 'minor', color='0.3', linestyle='dotted', linewidth=0.1) 98 99 yaxis_2.grid(True, 'major', color='0.3', linestyle='solid', linewidth=0.2) 100 yaxis_2.grid(True, 'minor', color='0.3', linestyle='dotted', linewidth=0.1) 101 102 103 104 #================================================================================================================================================== 105 #======= 绘图 106 #================================================================================================================================================== 107 xindex= numpy.arange(length) # X 轴上的 index,一个辅助数据 108 109 zipoc= zip(pdata[u'开盘'], pdata[u'收盘']) 110 up= numpy.array( [ True if po < pc and po != None else False for po, pc in zipoc] ) # 标示出该天股价日内上涨的一个序列 111 down= numpy.array( [ True if po > pc and po != None else False for po, pc in zipoc] ) # 标示出该天股价日内下跌的一个序列 112 side= numpy.array( [ True if po == pc and po != None else False for po, pc in zipoc] ) # 标示出该天股价日内走平的一个序列 113 114 115 116 volume= pdata[u'成交量'] 117 rarray_vol= numpy.array(volume) 118 volzeros= numpy.zeros(length) # 辅助数据 119 120 # XXX: 如果 up/down/side 各项全部为 False,那么 vlines() 会报错。 121 if True in up: 122 axes_2.vlines(xindex[up], volzeros[up], rarray_vol[up], color='red', linewidth=3.0, label='_nolegend_') 123 if True in down: 124 axes_2.vlines(xindex[down], volzeros[down], rarray_vol[down], color='green', linewidth=3.0, label='_nolegend_') 125 if True in side: 126 axes_2.vlines(xindex[side], volzeros[side], rarray_vol[side], color='0.7', linewidth=3.0, label='_nolegend_') 127 128 129 130 # 设定 X 轴坐标的范围 131 #================================================================================================================================================== 132 axes_2.set_xlim(-1, length) 133 134 135 136 # 设定 X 轴上的坐标 137 #================================================================================================================================================== 138 datelist= [ datetime.date(int(ys), int(ms), int(ds)) for ys, ms, ds in [ dstr.split('-') for dstr in pdata[u'日期'] ] ] 139 140 # 确定 X 轴的 MajorLocator 141 mdindex= [] # 每个月第一个交易日在所有日期列表中的 index 142 years= set([d.year for d in datelist]) # 所有的交易年份 143 144 for y in sorted(years): 145 months= set([d.month for d in datelist if d.year == y]) # 当年所有的交易月份 146 for m in sorted(months): 147 monthday= min([dt for dt in datelist if dt.year==y and dt.month==m]) # 当月的第一个交易日 148 mdindex.append(datelist.index(monthday)) 149 150 xMajorLocator= FixedLocator(numpy.array(mdindex)) 151 152 # 确定 X 轴的 MinorLocator 153 wdindex= [] # 每周第一个交易日在所有日期列表中的 index 154 for d in datelist: 155 if d.weekday() == 0: wdindex.append(datelist.index(d)) 156 157 xMinorLocator= FixedLocator(numpy.array(wdindex)) 158 159 # 确定 X 轴的 MajorFormatter 和 MinorFormatter 160 def x_major_formatter_2(idx, pos=None): 161 return datelist[idx].strftime('%Y-%m-%d') 162 163 def x_minor_formatter_2(idx, pos=None): 164 return datelist[idx].strftime('%m-%d') 165 166 xMajorFormatter= FuncFormatter(x_major_formatter_2) 167 xMinorFormatter= FuncFormatter(x_minor_formatter_2) 168 169 # 设定 X 轴的 Locator 和 Formatter 170 xaxis_2.set_major_locator(xMajorLocator) 171 xaxis_2.set_major_formatter(xMajorFormatter) 172 173 xaxis_2.set_minor_locator(xMinorLocator) 174 xaxis_2.set_minor_formatter(xMinorFormatter) 175 176 # 设定 X 轴主要坐标点与辅助坐标点的样式 177 for malabel in axes_2.get_xticklabels(minor=False): 178 malabel.set_fontsize(3) 179 malabel.set_horizontalalignment('right') 180 malabel.set_rotation('30') 181 182 for milabel in axes_2.get_xticklabels(minor=True): 183 milabel.set_fontsize(2) 184 milabel.set_horizontalalignment('right') 185 milabel.set_rotation('30') 186 187 188 189 # 设定 Y 轴坐标的范围 190 #================================================================================================================================================== 191 maxvol= max(volume) # 注意是 int 类型 192 axes_2.set_ylim(0, maxvol) 193 194 195 196 # 设定 Y 轴上的坐标 197 #================================================================================================================================================== 198 vollen= len(str(maxvol)) 199 200 yMajorLocator_2= MultipleLocator(10**(vollen-1)) 201 yMinorLocator_2= MultipleLocator((10**(vollen-2))*5) 202 203 # 确定 Y 轴的 MajorFormatter 204 # def y_major_formatter_2(num, pos=None): 205 # numtable= {'1':u'一', '2':u'二', '3':u'三', '4':u'四', '5':u'五', '6':u'六', '7':u'七', '8':u'八', '9':u'九', } 206 # dimtable= {3:u'百', 4:u'千', 5:u'万', 6:u'十万', 7:u'百万', 8:u'千万', 9:u'亿', 10:u'十亿', 11:u'百亿'} 207 # return numtable[str(num)[0]] + dimtable[vollen] if num != 0 else '0' 208 209 def y_major_formatter_2(num, pos=None): 210 return int(num) 211 yMajorFormatter_2= FuncFormatter(y_major_formatter_2) 212 213 # 确定 Y 轴的 MinorFormatter 214 # def y_minor_formatter_2(num, pos=None): 215 # return int(num) 216 # yMinorFormatter_2= FuncFormatter(y_minor_formatter_2) 217 yMinorFormatter_2= NullFormatter() 218 219 # 设定 X 轴的 Locator 和 Formatter 220 yaxis_2.set_major_locator(yMajorLocator_2) 221 yaxis_2.set_major_formatter(yMajorFormatter_2) 222 223 yaxis_2.set_minor_locator(yMinorLocator_2) 224 yaxis_2.set_minor_formatter(yMinorFormatter_2) 225 226 # 设定 Y 轴主要坐标点与辅助坐标点的样式 227 for malab in axes_2.get_yticklabels(minor=False): 228 malab.set_fontsize(3) 229 230 for milab in axes_2.get_yticklabels(minor=True): 231 milab.set_fontsize(2) 232 233 234 235 #================================================================================================================================================== 236 #================================================================================================================================================== 237 #======= K 线图部分 238 #================================================================================================================================================== 239 #================================================================================================================================================== 240 241 # 添加 Axes 对象 242 #================================================================================================================================================== 243 axes_1= figobj.add_axes(rect_1, axis_bgcolor='black', sharex=axes_2) 244 axes_1.set_axisbelow(True) # 网格线放在底层 245 246 if useexpo: 247 axes_1.set_yscale('log', basey=expbase) # 使用对数坐标 248 249 # 改变坐标线的颜色 250 #================================================================================================================================================== 251 for child in axes_1.get_children(): 252 if isinstance(child, matplotlib.spines.Spine): 253 child.set_color('lightblue') 254 255 # 得到 X 轴 和 Y 轴 的两个 Axis 对象 256 #================================================================================================================================================== 257 xaxis_1= axes_1.get_xaxis() 258 yaxis_1= axes_1.get_yaxis() 259 260 # 设置两个坐标轴上的 grid 261 #================================================================================================================================================== 262 xaxis_1.grid(True, 'major', color='0.3', linestyle='solid', linewidth=0.2) 263 xaxis_1.grid(True, 'minor', color='0.3', linestyle='dotted', linewidth=0.1) 264 265 yaxis_1.grid(True, 'major', color='0.3', linestyle='solid', linewidth=0.2) 266 yaxis_1.grid(True, 'minor', color='0.3', linestyle='dotted', linewidth=0.1) 267 268 269 270 #================================================================================================================================================== 271 #======= 绘图 272 #================================================================================================================================================== 273 274 # 绘制 K 线部分 275 #================================================================================================================================================== 276 rarray_open= numpy.array(pdata[u'开盘']) 277 rarray_close= numpy.array(pdata[u'收盘']) 278 rarray_high= numpy.array(pdata[u'最高']) 279 rarray_low= numpy.array(pdata[u'最低']) 280 281 # XXX: 如果 up, down, side 里有一个全部为 False 组成,那么 vlines() 会报错。 282 if True in up: 283 axes_1.vlines(xindex[up], rarray_low[up], rarray_high[up], color='red', linewidth=0.6, label='_nolegend_') 284 axes_1.vlines(xindex[up], rarray_open[up], rarray_close[up], color='red', linewidth=3.0, label='_nolegend_') 285 if True in down: 286 axes_1.vlines(xindex[down], rarray_low[down], rarray_high[down], color='green', linewidth=0.6, label='_nolegend_') 287 axes_1.vlines(xindex[down], rarray_open[down], rarray_close[down], color='green', linewidth=3.0, label='_nolegend_') 288 if True in side: 289 axes_1.vlines(xindex[side], rarray_low[side], rarray_high[side], color='0.7', linewidth=0.6, label='_nolegend_') 290 axes_1.vlines(xindex[side], rarray_open[side], rarray_close[side], color='0.7', linewidth=3.0, label='_nolegend_') 291 292 # 绘制均线部分 293 #================================================================================================================================================== 294 rarray_1dayave= numpy.array(pdata[u'1日权均']) 295 rarray_5dayave= numpy.array(pdata[u'5日均']) 296 rarray_30dayave= numpy.array(pdata[u'30日均']) 297 298 axes_1.plot(xindex, rarray_1dayave, 'o-', color='white', linewidth=0.1, markersize=0.7, markeredgecolor='white', markeredgewidth=0.1) # 1日加权均线 299 axes_1.plot(xindex, rarray_5dayave, 'o-', color='yellow', linewidth=0.1, markersize=0.7, markeredgecolor='yellow', markeredgewidth=0.1) # 5日均线 300 axes_1.plot(xindex, rarray_30dayave, 'o-', color='green', linewidth=0.1, markersize=0.7, markeredgecolor='green', markeredgewidth=0.1) # 30日均线 301 302 # 设定 X 轴坐标的范围 303 #================================================================================================================================================== 304 axes_1.set_xlim(-1, length) 305 306 307 308 # 先设置 label 位置,再将 X 轴上的坐标设为不可见。因为与 成交量子图 共用 X 轴 309 #================================================================================================================================================== 310 311 # 设定 X 轴的 Locator 和 Formatter 312 xaxis_1.set_major_locator(xMajorLocator) 313 xaxis_1.set_major_formatter(xMajorFormatter) 314 315 xaxis_1.set_minor_locator(xMinorLocator) 316 xaxis_1.set_minor_formatter(xMinorFormatter) 317 318 # 将 X 轴上的坐标设为不可见。 319 for malab in axes_1.get_xticklabels(minor=False): 320 malab.set_visible(False) 321 322 for milab in axes_1.get_xticklabels(minor=True): 323 milab.set_visible(False) 324 325 # 用这一段效果也一样 326 # pyplot.setp(axes_1.get_xticklabels(minor=False), visible=False) 327 # pyplot.setp(axes_1.get_xticklabels(minor=True), visible=False) 328 329 330 331 # 设定 Y 轴坐标的范围 332 #================================================================================================================================================== 333 axes_1.set_ylim(ylowlim_price, yhighlim_price) 334 335 336 337 # 设定 Y 轴上的坐标 338 #================================================================================================================================================== 339 340 if useexpo: 341 # 主要坐标点 342 #----------------------------------------------------- 343 yMajorLocator_1= LogLocator(base=expbase) 344 345 yMajorFormatter_1= NullFormatter() 346 347 # 设定 X 轴的 Locator 和 Formatter 348 yaxis_1.set_major_locator(yMajorLocator_1) 349 yaxis_1.set_major_formatter(yMajorFormatter_1) 350 351 # 设定 Y 轴主要坐标点与辅助坐标点的样式 352 # for mal in axes_1.get_yticklabels(minor=False): 353 # mal.set_fontsize(3) 354 355 # 辅助坐标点 356 #----------------------------------------------------- 357 minorticks= range(int(ylowlim_price), int(yhighlim_price)+1, 100) 358 359 yMinorLocator_1= FixedLocator(numpy.array(minorticks)) 360 361 # 确定 Y 轴的 MinorFormatter 362 def y_minor_formatter_1(num, pos=None): 363 return str(num/100.0) + '0' 364 365 yMinorFormatter_1= FuncFormatter(y_minor_formatter_1) 366 367 # 设定 X 轴的 Locator 和 Formatter 368 yaxis_1.set_minor_locator(yMinorLocator_1) 369 yaxis_1.set_minor_formatter(yMinorFormatter_1) 370 371 # 设定 Y 轴主要坐标点与辅助坐标点的样式 372 for mil in axes_1.get_yticklabels(minor=True): 373 mil.set_fontsize(3) 374 375 else: # 如果使用线性坐标,那么只标主要坐标点 376 yMajorLocator_1= MultipleLocator(100) 377 378 def y_major_formatter_1(num, pos=None): 379 return str(num/100.0) + '0' 380 381 yMajorFormatter_1= FuncFormatter(y_major_formatter_1) 382 383 # 设定 Y 轴的 Locator 和 Formatter 384 yaxis_1.set_major_locator(yMajorLocator_1) 385 yaxis_1.set_major_formatter(yMajorFormatter_1) 386 387 # 设定 Y 轴主要坐标点与辅助坐标点的样式 388 for mal in axes_1.get_yticklabels(minor=False): 389 mal.set_fontsize(3) 390 391 392 # 保存图片 393 #================================================================================================================================================== 394 figobj.savefig(figpath, dpi=figdpi, facecolor=figfacecolor, edgecolor=figedgecolor, linewidth=figlinewidth) 395 396 397 398 if __name__ == '__main__': 399 Plot(pfile=sys.argv[1], figpath=sys.argv[2], useexpo=True)