查看原文
其他

软件应用丨Python量化投资学习报告之二:热点获取及数据分析

小徐 数据Seminar 2021-06-04



昨天我们介绍了如何使用Python进行量化投资的学习报告之数据抓取,点击查看软件应用丨Python量化投资学习报告之一:数据抓取




热点获取


东方财富


这里我抓取的是东方财富的国内经济要闻,页面地址位于:

http://finance.eastmoney.com/news/cgnjj.html

这一页面的新闻列表不是通过Ajax异步加载的,可以直接抓取,而页码由url中cgnjj_x(x为页码)决定,抓取过程较为简单;这里统计的思路是,将所有的要闻简述拼接成字符串(以;分隔并写入txt作备份),然后利用jieba库分词并统计词频,项目地址:

https://github.com/CatsJuice/eastmoney-yaowen-keyword

也可以直接clone至本地:

git clone https://github.com/CatsJuice/eastmoney-yaowen-keyword.git

左右滑动查看更多



数据分析

在证券投资中,有很多技术层面的投资策略,如各种公式,通过策略可以筛选出股票,但并不意味着一定能盈利,而量化投资可以验证这一策略的可靠性,接下来就是我对若干策略的验证。


一、换手率分析


分析连续处于低换手率的股票,脱离低换手率后,出现连续处于高换手率,判断2个时期的收盘价均价,分析满足这一特征的股票的价格是否会上涨。
程序设计的思路如下:
  • 迭代日线数据文件
  • 判断是否是连续高换手率
  • 判断是否在连续高换手率后出现连续低换手率
  • 结果展示
注: 因为数据文件是按日期的倒序排序的,所以分析迭代时,先判断是否出现连续高换手率。
核心代码如下:
high = []low = []for row in arr: rate = row[10] if rate == "None": high = [] low = [] continue if rate == 0: if len(high) >= self.min_days and len(low) >= self.min_days: dic = {'high': high, 'low': low} self.res.append(dic) high = [] low = [] continue rate = float(rate) # 4. 判断是否是 高 换手率 if rate >= self.border_rate: # 4.3 判断是否是: 连续高 ->连续低 -> 结束连续低 if len(low) >= self.min_days: # 符合条件, 写入 dic = {'high': high, 'low': low} self.res.append(dic) high = [] low = [] elif len(low) > 0: # 连续低中有值 # 不满足连续 低, 重置 high = [] low = [] high.append(row) elif len(high) < self.min_days: # 是低换手率, 判断是否前面是连续高换手率 # 4.1 前面不是连续的高换手率, 重置高换手率数组 high = [] else: # 4.2 前面是连续高换手率, 地换手率写入 low.append(row)
# 判断日期是否已达到最后 if row[0] <= self.end_date: # 判断是否满足条件 if len(high) >= self.min_days and len(low) >= self.min_days: dic = {'high': high, 'low': low} self.res.append(dic) high = [] low = [] break

左右滑动查看更多

简易流程图如下:

程序可调整参数如下:

程序运行结果截图如下:

完整项目地址:
https://github.com/CatsJuice/low-switch-hand-rate
或者:
git clone https://github.com/CatsJuice/low-switch-hand-rate.git

左右滑动查看更多


二、CCI指标


1. 概念

顺势指标又叫CCI指标,CCI指标是美国股市技术分析 家唐纳德·蓝伯特(Donald Lambert)于20世纪80年代提出的,专门测量股价、外汇或者贵金属交易是否已超出常态分布范围。属于超买超卖类指标中较特殊的一种。波动于正无穷大和负无穷大之间。但是,又不需要以0为中轴线,这一点也和波动于正无穷大和负无穷大的指标不同。

2. 指标用法

1. 当CCI指标曲线在+100线~-100线的常态区间里运行时,CCI指标参考意义不大,可以用KDJ等其它技术指标进行研判。
2. 当CCI指标曲线从上向下突破+100线而重新进入常态区间时,表明市场价格的上涨阶段可能结束,将进入一个比较长时间的震荡整理阶段,应及时平多做空。
3. 当CCI指标曲线从上向下突破-100线而进入另一个非常态区间(超卖区)时,表明市场价格的弱势状态已经形成,将进入一个比较长的寻底过程,可以持有空单等待更高利润。如果CCI指标曲线在超卖区运行了相当长的一段时间后开始掉头向上,表明价格的短期底部初步探明,可以少量建仓。CCI指标曲线在超卖区运行的时间越长,确认短期的底部的准确度越高。
4. CCI指标曲线从下向上突破-100线而重新进入常态区间时,表明市场价格的探底阶段可能结束,有可能进入一个盘整阶段,可以逢低少量做多。
5. CCI指标曲线从下向上突破+100线而进入非常态区间(超买区)时,表明市场价格已经脱离常态而进入强势状态,如果伴随较大的市场交投,应及时介入成功率将很大。
6. CCI指标曲线从下向上突破+100线而进入非常态区间(超买区)后,只要CCI指标曲线一直朝上运行,表明价格依然保持强势可以继续持有待涨。但是,如果在远离+100线的地方开始掉头向下时,则表明市场价格的强势状态将可能难以维持,涨势可能转弱,应考虑卖出。如果前期的短期涨幅过高同时价格回落时交投活跃,则应该果断逢高卖出或做空。

3. 公式

关于公式有两种:

公式一:

TYP:=(HIGH+LOW+CLOSE)/3;CCI:(TYP-MA(TYP,N))/(0.015*AVEDEV(TYP,N));

左右滑动查看更多

该公式来自通达信,含义如下:
TYP赋值:(最高价+最低价+收盘价)/3输出CCI:(TYP-TYP的N日简单移动平均)/(0.015*TYP的N日平均绝对偏差)

左右滑动查看更多

公式二:

该公式摘录于:
https://www.joinquant.com/view/community/detail/219
程序中我采用的是公式二,生成的 CCI 指标追加到日线文件中,在相应日期后增加 CCI 的值,在 Python 中对 csv 文件进行增加列的方法,在网上可找到的方法较少,以下为一个提供了多种解决方案的,较为完善的链接: 
https://stackoverflow.com/questions/11070527/how-to-add-a-new-column-to-a-csv-file

我使用的是 pandas 库,关键代码如下:

df = pd.read_csv(filename, encoding='gbk')df['cci'] = ''for index, row in pd.iterrows(): df.loc[index, 'cci'] = cci

左右滑动查看更多

4. 程序设计

总共设计了4个类如下:

5. 参数说明

项目地址:
https://github.com/CatsJuice/stock-cci
或者clone:
git clone https://github.com/CatsJuice/stock-cci

左右滑动查看更多

6. 测试结果

在进行测试购买时,采用的策略有2种,如下:
策略一:
对应CCIAnalyze(object)中的test_buy(self)方法,当CCI向下突破-100时,后一交易日买入,等到CCI向上突破100时,后一交易日卖出(由于计算出当日的CCI,当日已不可买入/卖出,所以计算后一交易日买入/卖出)。
策略二:
对应CCIAnalyze(object)中的test_buy_2(self)方法, 当CCI向下突破-100时,继续观察,等到向下达到第一个峰值时,后一交易日买入,等到CCI向上突破100时,继续观察,等到向上达到第一个峰值时,后一交易日卖出(和策略一一样, 计算出CCI后只能后一日买入,而策略二不同的是,要判断达到第一个峰值, 必须确定峰值后一日CCI降低,所以测试时,购买的是峰值的后第2个交易日)。
经过若干次测试,分别使用的数据是2019-01-01 ~ 2019-04-26, 2018-01-01 ~ 2019-04-26, 2017-01-01 ~ 2019-04-26,测试的结果,购买策略一的收益率大概为56%,可见这个指标有一定依据,但是盈利的几率不够高; 而策略二的收益率大概为57%,较策略一高一点,但是依旧无法将其作为购买的唯一指标,以下是某次运行结果的部分截图:


三、《胡立阳股票投资100招》 由“价量关系”来为个股打分数


1. 概念

胡立阳根据股票的价量关系对股票进行打分(第21招),而其打分的依据如下:
当日个股表现:
(1)价涨量增 +2 分
(2)价涨量缩 +1 分
(3)价跌量增 -2 分
(4)价跌量缩 -1 分
每日累计评分。你只要连续计算一个星期,以最高分或者是评分稳定增加的作为你投资的第一选择,因为那只个股具备了“价量配合”的上涨条件。

2. 程序设计

根据以上打分标准,不难计算每只股票某日的得分,然后将所有得分排序即可, 这样能够得到某一天的所有股票打分,而需要验证每只股票是否盈利,这里我简单地判断后一天收盘价是否比当天高,所以能够得出每只股票是否盈利,计算出当天得分前n只股票的盈利与否,再用盈利股票的只数 / n 即当天的盈利率;
以此类推m天可得出m天这一策略的盈利几率。
程序流程图如下:

参数详情如下表:

3. 结果

键值说明:date: 统计的日期rate_low: 打分最低的盈利几率rate_high: 打分最高的盈利几率
在分别统计了每个分数的股票的盈利率,和排名靠前的股票的盈利率来看,胡立阳的打分欠缺科学性,盈利率不稳定,不建议做参考;

4. Source Code

项目地址:

https://github.com/CatsJuice/stock-price-num-score
直接clone:
git clone https://github.com/CatsJuice/stock-price-num-score.git

左右滑动查看更多


四、移动平均线分析


1. 概念

移动平均线,Moving Average,简称 MA,MA 是用统计分析的方法,将一定时期内的证券价格(指数)加以平均,并把不同时间的平均值连接起来,形成一根 MA ,用以观察证券价格变动趋势的一种技术指标。
常见的均线有5日均线(MA5),十日均线(MA10),二十日均线(MA20),三十日均线(MA30),六十日均线(MA60)

2. 计算

在这里,我的计算方法是简单地计算 n 日内的 收盘价 的算数平均值。

3. 程序设计

基本思路如下:
  • 定义要计算的几日(假设为n)均线数组,进行遍历计算
  • 迭代文件列表
  • 打开某个文件,选取 n 行,计算这n行的 收盘价 总和
  • 迭代 csv 文件的每一行,每次迭代,将上述收盘价的总和减去首行,追加新的一行
  • 计算 ma 并写入原文件
和之前计算cci不同,这里不再做多余的迭代,以此能大大提高效率

4. 参数说明

5. 购买策略分析

策略一:'老太太选股法' 一根均线打天下

购买的策略较为简单,收盘价低于均线即买入,将收盘价作为买入价格(假设第二个交易日开盘即买入),收盘价高于均线即卖出,同样将收盘价作为卖出价格

策略二:'黄金交叉'和'死亡交叉'

'黄金交叉' 和 '死亡交叉' 的概念来自日均线的百度百科:
1、”黄金交叉”当10日均线由下往上穿越30日均线,形成10日均线在上,30日均线在下时,其交叉点就是黄金交叉,黄金交叉是多头的表现,出现黄金交叉后,后市会有一定的涨幅空间,这是进场的最佳时机。
2、”死亡交叉”当10日均线由上往下穿越30日均线,形成30日均线在上,10日均线在下时,其交点称之为”死亡交叉”,”死亡交叉”预示空头市场来临,股市将下跌此时是出场的最佳时机。

6. 分析结果

老太太选股法:

对于'老太太选股法',我分别测试了5日均线和10日均线(由于20日数据较大,电脑处理太慢,暂时不考虑),最终得到的结果为:
ma5 的盈利几率为63.3132%; 而 ma10 的盈利几率为67.0026%
以下是某次运行的截图:

运行结果中,可以发现,无论是盈利或是亏损,金额基本都在1元以内,可见这一策略首先具有一定的科学性,同时风险也不是很大,但盈利金额较高的可能性很小;
对此,我增加了结果判断,统计亏损 / 盈利超过 1 元的比例,以下是结果:
对于 ma5:

对于 ma10:

对于 ma20:

对于 ma30:

对于 ma60:

从更详细的结果可以看到,无论盈利或亏损,超过 1 元的概率都不大,但是亏损的时候超过 1 元的概率比盈利大;更有趣的是,当 ma的计算天数越多,盈利的几率越大,由于一般看盘软件中仅提供了上述这些均线(MA5, MA10, MA20, MA30, MA60), 所以这里不再多更高的天数进行测试;虽然天数越大时,盈利几率越高,但是从更细节数据可以看到,盈利的情况大于1元的概率始终在 10% ~ 15%,而亏损时超过 1 元的概率却表现出和天数正相关的趋势,并且从 24% 跳跃到高达 50%。

'黄金交叉'和'死亡交叉':

运行结果如图:

对于这个结果的确是非常出乎意料,这个盈利率低得感觉好像可以作为一个反向指标使用;
对于这个结果,首先应该怀疑是自己的代码逻辑出现了问题,于是我检查了关键代码如下:
# 前一天 ma30 > ma10 , 今天ma10 >= ma30if prev_row is not None and prev_row['ma30'] > prev_row['ma10'] and row['ma10'] >= row['ma30']: # 买入
......
# 前一天 ma30 < ma10 , 今天ma10 <= ma30if prev_row['ma30'] < prev_row['ma10'] and row['ma10'] <= row['ma30']: # 卖出

左右滑动查看更多

关键代码部分好像并没有什么逻辑问题,但是不排除其他部分出现问题,由于这个指标的预期过于不符,我试着尝试了交换 '黄金交叉'和'死亡交叉',修改上述关键代码如下(其实就是把所有大小判断符号反置):
# 前一天 ma30 < ma10 , 今天ma10 <= ma30if prev_row is not None and prev_row['ma30'] < prev_row['ma10'] and row['ma10'] <= row['ma30']: # 买入
......
# 前一天 ma30 > ma10 , 今天ma10 >= ma30if prev_row['ma30'] > prev_row['ma10'] and row['ma10'] >= row['ma30']: # 卖出

左右滑动查看更多

以下为运行结果:

这时候盈利率可以说是马马虎虎还算过得去了,但是盈利率却还是不如 ‘老太太选股法’

7. Source Code

项目地址:

https://github.com/CatsJuice/line-of-ma
直接 clone :
git clone https://github.com/CatsJuice/line-of-ma.git

左右滑动查看更多


五、分时数据 成交手分析


1. 基本概念

成交手指的是每笔买卖流动的股票数量,现行股票交易所用手方便表示成交数量,一手相当于股票一百股,各类炒股软件中的成交量一般用手表示

2. 分析的形态

当某日开盘初出现集中的大量 买入/卖出 ,即成交手出现连续峰值的情况(大概如下图所示情形),分析该股在当日的趋势

3. 程序设计

设计的关键,是为了找出某日开盘时的集中峰值,在这里我首先设定分界线, 默认 10 点前为开盘初,统计全天的换手量平均值,以及开盘初的换手量平均值,当开盘初的换手量大于全天的某倍数(默认为2)时,即认定当日开盘初的换手量为集中峰值;
但将今日的换手量平均值作为比较,在实际运用中可能无法实施,所以另一策略是统计 前一日 的平均换手量作为参考(在此尚未实现)。

4. 参数说明

5. 结果及分析

以下为不区分买盘或卖盘,倍数为 2 时,当天前后均价变化是否上涨的结果:

而只考虑买盘时,这个几率为34%,只考虑卖盘时几率为47%,可见开盘初集中的成交量不一定会导致今日价格走上升的趋势,但在这个部分的程序设计中,分析得不是很到位,首先实际中没法考虑当天后面的成交量情况,所以更稳妥的方法应该是计算 前一交易日的平均成交量 ,而在判断这这股票是否处于上涨的趋势时,简单判断今日的前后均价也不是最不合理的。

6. Source Code

项目地址为:

https://github.com/CatsJuice/stock-volume-analyze
直接 clone:
git clone https://github.com/CatsJuice/stock-volume-analyze.git

左右滑动查看更多


六、MACD


1. 基本概念

MACD百度百科的解释:MACD 称为异同移动平均线,是从双指数移动平均线发展而来的,由快的指数移动平均线( EMA12 )减去慢的指数移动平均线( EMA26 )得到快线 DIF ,再用 2 ×(快线DIF-DIF的9日加权移动均线DEA) 得到 MACD柱。

MACD 金叉MACD 指标是股票技术分析中一个重要的技术指标,由两条曲线和一组红绿柱线组成。两条曲线中波动变化大的是 DIF 线,通常为白线红线,相对平稳的是  DEA 线(MACD线),通常为黄线。当 DIF 线上穿 DEA 线时,这种技术形态叫做金叉,通常为买入信号。

MACD 死叉DIF 由上向下突破 DEA,为卖出信号

2. 程序设计

数据计算:

【MACD公式】

以下为通达信的公式:
DIF:EMA(CLOSE,SHORT)-EMA(CLOSE,LONG);DEA:EMA(DIF,MID);MACD:(DIF-DEA)*2,COLORSTICK;

左右滑动查看更多

需要的变量参数为 SHORT , LONG , MID, 在通达信中默认分别为 12 , 26 , 9;要计算 MACD ,关键在于求 EMA(指数移动平均值), 其公式如下:

其中, α 为平滑指数, 一般取 2 / ( N + 1 )
该公式是依赖于前一日的递归的计算,最大的问题便是第一天的 EMA[yesterday] 无法求得,在程序设计时, 我将它设置为当日收盘价,再求 DIF, 第一天的 DIF 就变成了 0 (当日收盘价 - 当日收盘价), 而求 DEA (即 DIF 的 EMA) 时,第一天所需的 DEA[yesterday] 同样不知道, 将其设置为 DIF ( 即 0 )。
【性能优化之多线程】
由于数据量较大, 在这里我尝试使用 Python 的多线程进行分析, 添加了一个 calculate_all_by_thread 的方法, 接收 1 个参数(thread_num)即需要的线程数, 然后根据线程数动态创建线程, 将文件划分为等分的块分别计算, 创建线程部分的代码如下:
def calculate_all_by_thread(self, thread_num): file_list = os.listdir(self.file_prefix) file_count = len(file_list) offset = file_count / thread_num offset = math.ceil(offset) threads = [] for i in range(thread_num): start = i * offset end = (i+1) * offset if (i+1) * offset < file_count else -1 thread = threading.Thread(target=self.calculate_block, args=(start, end)) threads.append(thread) for t in threads: t.setDaemon(True) t.start() t.join()

左右滑动查看更多

这里我使用的 2 个线程进行计算, 实际测试速度虽然不是单线程的 2 倍,但是计算的时间上是有所优化的。
【数据存储】
在了解公式后, 便可迭代文件进行带入计算, 计算结果这里我采用的是将其写入原文件, 在列末尾追加新的 5 列:
  • EMA26 (26根据传参而定)
  • EMA12 (12根据传参而定)
  • DIF
  • DEA
  • MACD
【数据验证】
完成计算后, 有必要对计算结果进行验证, 这里, 我简单写了一个输出来进行简单验证, 该部分代码如下:
def verify_calculate(self, code): try: df = pd.read_csv(self.file_prefix + str(code) + '.csv', encoding='gbk') except: print('文件%s.csv打开失败' % code) return df = df[df.日期 > self.end_date] df = df[::-1] mid = [] dates = [] difs = [] deas = [] macds = [] color_macd = [] for index, row in df.iterrows(): mid.append(0) dates.append(row['日期']) difs.append(row['DIF']) deas.append(row['DEA']) macds.append(row['MACD']) if row['MACD'] > 0: color_macd.append('red') else: color_macd.append('green') # 绘制图表 fig = plt.figure(dpi=128, figsize=(100, 6)) plt.plot(dates, mid, c='blue') plt.plot(dates, difs, c='yellow') plt.plot(dates, deas, c='black') plt.bar(dates, macds, color=color_macd) plt.xticks(fontsize=5) plt.xlabel('', fontsize=5) fig.autofmt_xdate() plt.show()

左右滑动查看更多

在对股票 '000002' 进行验证, 验证结果如下(上图为通达信MACD截图, 下图为程序输出的图片, 时间为2018-01-01 ~ 2019-04-26):

可以看到图形基本一致, 但是同样对股票 000001 进行验证, 结果如下:

此时, 两图形基本没有重合, 原因是因为计算 MACD 时, 第一天的 EMA并不是前一天计算的,同样 DEA 也是, 而这里数据只计算了 2018-01-01 ~ 2010-04-26, 所以前面基本一致的时候, 是因为第一日刚好相近导致后面计算偏差不大, 对此, 应该不设置截止日期以保证数据的可靠性, 我将时间设置到了 2014-01-01, 重新验证 000001 得到如下:

然后再随机抽取一直股票进行验证, 如下为 600702 的结果

数据分析

这里主要是对 MACD 的分析, 所以以上计算的数据实际上只要用到 MACD , 对 MACD 柱满足以下形态的股票进行分析:

该形态需满足以下特点:
  • 出现连续的红色块(MACD > 0)
  • 紧接着出现连续蓝色块(MACD < 0)
  • 蓝色块小于第一个红色块
  • 蓝色块后面跟着一个红色块, 且后一红色块大于前一红色块
在程序设计时, 使用的是迭代数据行, 通过 if 判断, 来定位上述的 3 个块, 在第三块大于第一块时即为符合条件的形态;大致思路是, 定义 3 个变量来标记 3 块的合计值:
  • red_1
  • green_1
  • red_2
判断三个块的条件分别为:
  • 块 1 :green_1 == 0 and red_2 == 0
  • 块 2 :red_1 != 0 and red_2 == 0
  • 块 3 :red_1 != 0 and red_2 != 0 and green_1 != 0

3. 参数说明

4. function注释

  • calculate_noe
    • 计算单只股票的 MACD 、 DIF 、 EMA 、 DEA
    • 需要参数:
      code: 股票的代码
    • 返回值:
      None
  • calculate_all_by_thread
    • 通过多线程计算所有股票
    • 需要参数:
      thread_num: 线程数量
    • 返回值:
      None
  • calculate_block:
    • 计算指定代码块的股票
    • 需要参数:
      start:块下标开始
      end: 块下标结束
    • 返回值:
      None
  • verify_calculate
    • 验证计算的结果
    • 需要参数:
      code: 股票的代码
    • 返回值:
      None
  • analyze_macd_one:
    • 分析单只股票 的 macd
    • 需要参数:
      code: 股票的代码
    • 返回值:
      None
  • analyze_macd_by_thread:
    • 通过多线程分析所有股票
    • 需要参数:
      thread_num: 线程数量
    • 返回值:
      None
  • analyze_block:
    • 分析指定代码块的股票
    • 需要参数:

      start:块下标开始
      end: 块下标结束
    • 返回值:
      None
  • show_res:
    • 输出分析结果并写入相关文件
    • 需要参数:None
    • 返回值:None

5. 结果分析

【某次结果截图】

计算 MACD 时候的传参均为默认, 即通达信公式中的默认值, 对上述情况进行分析得到的结果是, 后 10 天内至少 6 天比符合条件时涨了的比例约为 55% , 而如果将参数调整为  5 天内至少 3 天, 则比例降低到约47%, 而如果不是 MACD 的值计算有误的话, 可见这个 MACD 的指标并不是很可靠, 但可以调整计算 MACD 的参数的值

6. Source Code

项目地址:https://github.com/CatsJuice/MACD-Analyze
直接 clone:
git clone https://github.com/CatsJuice/MACD-Analyze.git
左右滑动查看更多






今日推荐




搜索你感兴趣的内容吧



软件应用丨Python量化投资学习报告之一:数据抓取


统计计量丨陈强:应用计量经济学的常见问题


软件应用丨吐血推荐,B站最强学习资源汇总(数据科学,机器学习,python)





数据Seminar




这里是大数据、分析技术与学术研究的三叉路口



作者:小徐(徐志剑)排版编辑:青酱




    欢迎扫描👇二维码添加关注    


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存