研报复现系列(三):【东莞证券】股吧里说了什么?——基于文本舆情构建股市情绪指标
1.研报概述
本文是研报复现系列的第三篇,本文复现了【东莞证券】的研报【股吧里说了什么?——基于文本舆情构建股市情绪指标】
该研报试图利用文本情感分析,通过统计情绪词,将股民的评论进行情感分析,联系情绪词与指数的相关性,并由此为根据来进行买入与卖出等操作。
2.研究环境
pycharm
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
import json
import jieba
import re
import pandas as pd
import math
import tushare as ts
import datetime
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
3.研报复现
3.1数据获取
本文数据中的上证指数来自于tushare,评论文本爬取自东方财富股吧。
3.2数据预处理
将数据预处理分为两步:
step1:将上万条评论划分为一系列的词,然后取用哈工大的停用词表,并且筛选出这些停用词,将其去除。
step2:将研报中出现的情绪词进行词频统计,并画出图像。
sentiment_words = {'盈利':1,'涨':1,'反弹':1,'上涨':1,'开心':1,'赚':2,'涨停':2,'新高':1,'牛市':1,'有戏':1,'满意':1,'快乐':1,'大涨':2,'突破':1,'亏损':-1,'跌':-1,'回调':-1,'下跌':-1,'伤心':-1,'亏':-2,'杀跌':-2,'新低':-1,'熊市':-1,'完蛋':-1,'失望':-1,'郁闷':-1,'跌停':-2,'调整':-1
}
stopwords = [i.strip() for i in open('stopwords-master\hit_stopwords.txt','r',encoding='utf8').readlines()]#停用词
def pretty_cut(sentence):cut_list = jieba.lcut(''.join(re.findall('[\u4e00-\u9fa5]', sentence)), cut_all = False)for i in range(len(cut_list)-1, -1, -1): if cut_list[i] in stopwords:del cut_list[i]return cut_list
with open('数据Json\dfcf_tb_scomment.json','r',encoding='utf8') as f: data_json = f.read()
data_dic = json.loads(data_json)
json.dumps()
data_df = {'content':[],'date':[]
}
for item in data_dic:if item['share_code'] == 'zssh000001':data_df['content'].append(item['content'])data_df['date'].append(item['date'])
data_df = pd.DataFrame(data_df)
with open('words.txt','r',encoding='utf8') as f:words_list = f.read().split(' ')words_list = [word.strip() for word in words_list]
word_dic = {}
for i,content in enumerate(data_df['content']):print(i)sentence_words = pretty_cut(content)for word in sentence_words:if word_dic.get(word):word_dic[word] += 1else:word_dic[word] = 1
word_df = pd.DataFrame({'word':word_dic.keys(),'count':word_dic.values()})
word_df = word_df.sort_values(by='count',ascending=False).reset_index(drop=True)
word_df.to_excel('word_df.xlsx')
sentiment_count = {}
for sentiment_word in sentiment_words.keys():sentiment_count[sentiment_word] = word_dic.get(sentiment_word)
sorted_word = sorted(word_dic.items(),key=lambda d:d[1],reverse=True)
print(sorted_word[:int(len(sorted_word)*0.1)])
print(sentiment_count)
for i in range(len(sorted_word)-1,-1,-1):if not sorted_word[i][0] in words_list:del sorted_word[i]df = pd.read_excel('word_df.xlsx')
df1=df[df['word'].isin(sentiment_words.keys())]
figure = plt.figure(figsize=(30,14))
plt.bar(df1['word'], df1['count'], label='graph 1')
plt.xticks(fontsize=30)
plt.yticks(fontsize=30)
plt.show()
3.3 根据情绪词进行模型建立
step1:本文研报中将情绪词分为积极情绪词与消极情绪词,并且将这些情绪词赋上分值,积极情绪词例如“盈利”“赚”等赋大小不等的正值,消极情绪词则赋大小不等的负值。
step2:统计时间段2017.06.03 到2019.12.05 每日的情绪词得分,并画出走势图。
step3:画出上证指数的走势图。将两者进行对比,并得出线性相关系数。
data_by_date = data_df.groupby(by='date')
word_count_by_date={}
for date,data in data_by_date:word_dic = {}for content in data['content']:sentence_words = pretty_cut(content)for word in sentence_words:if word in sentiment_words.keys():if word_dic.get(word): word_dic[word] += 1else:word_dic[word] = 1word_count_by_date[date]=word_dic
print(word_count_by_date)
with open('words.txt','r',encoding='utf8') as f:words_list = f.read().split(' ')words_list = [word.strip() for word in words_list]
def calc_sentiment(count,value):sum=0for item in count.keys():sum+=value[item]*count[item]return sum
date_score={}
for date in word_count_by_date.keys():num = data_by_date.get_group(date).shape[0]date_score[date]=(calc_sentiment(word_count_by_date[date],sentiment_words))
print(date_score)
date_score_df=pd.DataFrame({'date':date_score.keys(),'score':date_score.values()})
date_score_df.to_excel('date_score_df1.xlsx')
pro = ts.pro_api('')#此处用自己的id,本处不提供作者id
index_df = pro.index_daily(ts_code='000001.SH', start_date='20170603', end_date='20191205')
index_df['date'] = [datetime.datetime.strptime(d,'%Y%m%d')for d in index_df['trade_date']] #作图只想显示月和日
plt.plot(index_df['date'],index_df['close'])
index_df = index_df.set_index('date')
plt.twinx()
df = pd.read_excel('date_score_df1.xlsx')
date = [datetime.datetime.strptime(d,'%Y-%m-%d')for d in df['date']] #作图只想显示月和日
plt.plot(date,df['score'],color='red')
plt.show()
df['date'] = date
df=df.set_index('date')
df['price'] = index_df['close']
df = df.dropna()
df = df.drop('Unnamed: 0',axis=1)
df['chg_pct'] = df['price'].pct_change()
df['chg_pct'] = df['chg_pct'].shift(1)
df['price_ma'] = df['price'].rolling(window=10).mean()
df['score_ma'] = df['score'].rolling(window=10).mean()
df = df.dropna()
print(df)
plt.plot(list(df.index),df['price_ma'])
plt.twinx()
plt.plot(list(df.index),df['score_ma'],color='red')
plt.show()
df=df.dropna()
print(df)
print(df.corr())
3.4情绪指标策略的构建与实现
本文研报中,用N日加权移动平均值作为原情绪指标。本文研报复现将N确定为10日。
假设情绪数据披露具有一天的滞后性,因此在第二日决定是否进行交易操作。
当其当日的情绪指标大于前10日的加权平均值,则在第二日买入,反之卖出。
得出策略表现中的策略收益率,基准收益率,胜率,盈亏比与最大撤回等统计量,见下述策略的统计指标表格。
def calculate_statistics(df:pd.DataFrame):'''输入:df:DataFrame类型position列:仓位标志位,0表示空仓,1表示持有标的flag列:买入卖出标志位,1表示在该时刻买入,-1表示在该时刻卖出close列:日收盘价输出:dict类型'''# 净值序列
df['net_asset_value'] = (1+df.close.pct_change(1).fillna(0)*df.position).cumprod()
df['index_net_value'] = (1+df.close.pct_change(1).fillna(0)).cumprod()
df['net_asset_pct_chg'] = df.net_asset_value.pct_change(1).fillna(0)
# 总收益率与年化收益率
total_return = df['net_asset_value'][df.shape[0] - 1] - 1
annual_return = (total_return+1) ** (1 / (df.shape[0] / 252)) - 1
total_return = total_return * 100
annual_return = annual_return * 100
# 夏普比率
df['ex_pct_chg'] = df['net_asset_pct_chg']
sharp_ratio = df['ex_pct_chg'].mean() * math.sqrt(252) / df['ex_pct_chg'].std()
# 回撤
df['high_level'] = (df['net_asset_value'].rolling(min_periods=1, window=len(df), center=False).max()
)
df['draw_down'] = df['net_asset_value'] - df['high_level']
df['draw_down_percent'] = df["draw_down"] / df["high_level"] * 100
max_draw_down = df["draw_down"].min()
max_draw_percent = df["draw_down_percent"].min()
# 持仓总天数
hold_days = df['position'].sum()
# 交易次数
trade_count = df[df['flag'] != 0].shape[0] / 2
# 平均持仓天数
avg_hold_days = int(hold_days / trade_count)
# 获利天数
profit_days = df[df['net_asset_pct_chg'] > 0].shape[0]
# 亏损天数
loss_days = df[df['net_asset_pct_chg'] < 0].shape[0]
# 胜率(按天)
winrate_by_day = profit_days / (profit_days + loss_days) * 100
# 平均盈利率(按天)
avg_profit_rate_day = df[df['net_asset_pct_chg'] > 0]['net_asset_pct_chg'].mean() * 100
# 平均亏损率(按天)
avg_loss_rate_day = df[df['net_asset_pct_chg'] < 0]['net_asset_pct_chg'].mean() * 100
# 平均盈亏比(按天)
avg_profit_loss_ratio_day = avg_profit_rate_day / abs(avg_loss_rate_day)
# 每一次交易情况
buy_trades = df[df['flag'] == 1].reset_index()
sell_trades = df[df['flag'] == -1].reset_index()
result_by_trade = {'buy': buy_trades['close'],'sell': sell_trades['close'],'pct_chg': (sell_trades['close'] - buy_trades['close'])/buy_trades['close']
}
result_by_trade = pd.DataFrame(result_by_trade)
# 盈利次数
profit_trades = result_by_trade[result_by_trade['pct_chg'] > 0].shape[0]
# 亏损次数
loss_trades = result_by_trade[result_by_trade['pct_chg'] < 0].shape[0]
# 单次最大盈利
max_profit_trade = result_by_trade['pct_chg'].max()
# 单次最大亏损
max_loss_trade = result_by_trade['pct_chg'].min()
# 胜率(按次)
winrate_by_trade = profit_trades / (profit_trades + loss_trades) * 100
# 平均盈利率(按次)
avg_profit_rate_trade = result_by_trade[result_by_trade['pct_chg'] > 0]['pct_chg'].mean()
# 平均亏损率(按次)
avg_loss_rate_trade = result_by_trade[result_by_trade['pct_chg'] < 0]['pct_chg'].mean()
# 平均盈亏比(按次)
avg_profit_loss_ratio_trade = avg_profit_rate_trade / abs(avg_loss_rate_trade)
statistics_result = {'net_asset_value': df['net_asset_value'][df.shape[0] - 1], # 最终净值'total_return': total_return, # 收益率'annual_return': annual_return, # 年化收益率'sharp_ratio': sharp_ratio, # 夏普比率'max_draw_percent': max_draw_percent, # 最大回撤'hold_days': hold_days, # 持仓天数'trade_count': trade_count, # 交易次数'avg_hold_days': avg_hold_days, # 平均持仓天数'profit_days': profit_days, # 盈利天数'loss_days': loss_days, # 亏损天数'winrate_by_day': winrate_by_day, # 胜率(按天)'avg_profit_rate_day': avg_profit_rate_day, # 平均盈利率(按天)'avg_loss_rate_day': avg_loss_rate_day, # 平均亏损率(按天)'avg_profit_loss_ratio_day': avg_profit_loss_ratio_day, # 平均盈亏比(按天)'profit_trades': profit_trades, # 盈利次数'loss_trades': loss_trades, # 亏损次数'max_profit_trade': max_profit_trade, # 单次最大盈利'max_loss_trade': max_loss_trade, # 单次最大亏损'winrate_by_trade': winrate_by_trade, # 胜率(按次)'avg_profit_rate_trade': avg_profit_rate_trade, # 平均盈利率(按次)'avg_loss_rate_trade': avg_loss_rate_trade, # 平均亏损率(按次)'avg_profit_loss_ratio_trade': avg_profit_loss_ratio_trade # 平均盈亏比(按次)
}
return df,statistics_result
df['flag'] = 0
df['position'] = 0
for i in range(1,len(df.index)-1):if df['score'][i-1]>df['score_ma'][i-1]:df['flag'][i]=1df['position'][i+1] = 1elif df['score'][i-1]<df['score_ma'][i-1]:df['flag'][i]=-1df['position'][i+1] = 0else:df['position'][i+1] = df['position'][i]
df = df.rename(columns={'price':'close'})
df,statistics_result = calculate_statistics(df)
plt.plot(df.index,df['net_asset_value'],color='red')
plt.plot(df.index,df['index_net_value'])
plt.show()
print(statistics_result)
策略的统计指标
统计量 | 数值 | |
---|---|---|
0 | 净值 | 1.028803191 |
1 | 收益率 | 2.88% |
2 | 年化收益率 | 1.19% |
3 | 夏普比率 | 0.16 |
4 | 最大回撤 | -12.40 |
5 | 持仓天数 | 342 |
6 | 交易次数 | 301 |
7 | 平均持仓天数 | 1 |
8 | 盈利天数 | 175 |
9 | 亏损天数 | 167 |
10 | 胜率(按天) | 51.17% |
11 | 平均盈利率(按天) | 0.69% |
12 | 平均亏损率(按天) | -0.70% |
13 | 平均盈亏比(按天) | 0.99 |
14 | 盈利次数 | 118 |
15 | 亏损次数 | 143 |
16 | 单次最大盈利 | 25.48% |
17 | 单次最大亏损 | -16.9% |
18 | 胜率(按次) | 45.21% |
19 | 平均盈利率(按次) | 8.44% |
20 | 平均亏损率(按次) | -7.19% |
21 | 平均盈亏比(按次) | 1.17 |
4.总结
本文基本复现了【东莞证券】的研报【股吧里说了什么?——基于文本舆情构建股市情绪指标】的研究内容,基本实现了原研报的研究步骤,但由于数据集的不同,所得的结果与原研报有较大的差异。但是这种引入投资者社区评论文本的思路是值得深入挖掘的,将以文本数据,卫星数据等另类数据融入策略的方法也是量化投资的一个趋势。
本文的不足:
1.本文并未检测N的选取是否准确,并未深入研究,而是将其主观赋值。
2.本研报在量化情绪因子时采用的方法过于简略,主观性太强,其对每个情绪词所赋的权重没有给出较好的解释。
5.优化方法
1.借助自然语言处理来量化一条评论的情绪指数。
2.可以根据评论的点赞数,跟贴数以及发布评论的用户在股吧的等级,粉丝数等数据给每条评论赋予不同的权重,根据加权的每日评论情绪量化出今日市场的总体情绪。
6.本文作者
舒意茗哈尔滨工业大学威海校区 汽车工程学院
蔡金航 哈尔滨工业大学威海校区 计算机科学与技术学院
写在最后
我们是国内普通高校的在校学生,同时也是量化投资的初学者。我们的学校不是清北复交,也没有金融工程实验室,同时地处三线小城,因此我们在校期间较难获得量化实习机会,但我们期待与业界进行沟通、交流。
蔡金航同学是我们其中的一员。其在寻找暑期量化实习时,收到了几家私募和券商金工组的笔试邀请,笔试内容皆为在给定时间内复现出一篇金工研报。蔡同学受到启发,发觉复现金工研报是我们学习量化策略、锻炼程序设计能力同时也是与业界交流的很好的途径。
在蔡同学的建议下,我们开启研报复现系列的创作,记录我们的学习过程,并将我们的创作内容分享出来,与读者们一起交流、学习、进步。
我们的水平有限,创作的内容难免会有错误或不严谨的内容,我们欢迎读者的批评指正。
如果您对我们的内容感兴趣,请联系我们:cai_jinhang@foxmail.com
研报复现系列(三):【东莞证券】股吧里说了什么?——基于文本舆情构建股市情绪指标相关推荐
- 研报复现系列(一):【方正证券】跟踪聪明钱:从分钟行情数据到选股因子
1.研报概述 本文是研报复现系列的第一篇,文本复现了[方正证券]的研报[跟踪聪明钱:从分钟行情数据到选股因子]. 该研报尝试从分钟行情数据中挖掘出那些聪明人(即机构)所做的交易,称为"聪明钱 ...
- 研报复现系列(二):【光大证券】基于阻力支撑相对强度(RSRS)的市场择时
1.研报概述 本文是券商金工研报复现系列的第二篇,文本复现了[光大证券]的[基于阻力支撑相对强度(RSRS)的市场择时]. 阻力位与支撑位传统的应用方法一般是选取特定的阻力位.支撑位作为阈值来进行突破 ...
- 研报复现系列(五)【光大证券】放量恰是入市时:成交量择时初探
前言 我们是国内普通高校的在校学生,同时也是量化投资的初学者.我们的学校不是清北复交,也没有金融工程实验室,同时地处三线小城,因此我们在校期间较难获得量化实习机会,但我们期待与业界进行沟通.交流. 蔡 ...
- 精英任务 | 第二期券商研报复现挑战赛
你是否不知道该如何在量化投资的路上继续向前? 你是否还在苦于没有研究的思路和方向? 你是否还在纠结几千份研报中,到底该去看哪一个? 研报复现第二期来啦,快来参加吧~ 我们从大量的研报中筛选出有意思的研 ...
- 精英任务 | 券商研报复现挑战赛
你是否不知道该如何在量化投资的路上继续向前? 你是否还在苦于没有研究的思路和方向? 你是否还在纠结几千份研报中,到底该去看哪一个? 来参加聚宽的精英任务吧~ 我们从大量的研报中筛选出了有意思的内容,来 ...
- 券商研报复现挑战赛|回望来时路,砥砺再出发
第一期的研报复现大赛已经落下帷幕啦~ 再次感谢朋友们的踊跃参与和大力支持~ 所有的匍匐都只是高高跃起前的热身, 第二期的赛事即将开启,我们期待您的参与! 第一期研报复现结果回顾 第一期研报复现比赛已经 ...
- 【每周研报复现】基于阻力支撑相对强度(RSRS)的市场择时
原创文章第106篇,专注"个人成长与财富自由.世界运作的逻辑, AI量化投资". 今天要复现的研报是:"光大证券_金融工程深度:基于阻力支撑相对强度(RSRS)的市场择时 ...
- Vitu研报复现挑战,来了解一下
传统金融里,券商每年都会产生大量有价值的研报,互联网的普及更是让我们每一个人都可以阅读到其内容. 在做投资决策前,我们需要信任这些研究,那么我们必须能够复现它.所以Vitu.AI正在通过可复现性挑战鼓 ...
- 阻力支撑指标RSRS策略:光大证券研报复现
昨天我们已经计算好了RSRS指标策略,今天把光大证券的研报复现一下. 由于计算比较耗时,我们会把计算的中间结果的dataframe保存下来. 我们使用hdf5保留数据结果,这里有一个小tip,有点奇怪 ...
最新文章
- Exploring Data with Python免费电子书
- php多规格多价格,不同规格的不同价格是怎么实现的呢?? 看这里
- 二叉树的链式结构的非递归遍历
- 情感分析朴素贝叶斯_朴素贝叶斯推文的情感分析
- python找人_python之找最后一个人
- Java学习-Overload和Override的区别
- Python 做自动化测试环境搭建
- 大数据智能营销有什么优势
- vue click.stop阻止点击事件继续传播
- 在linux中安装Jupyter notebook
- 电子书 杜春雷 ARM体系结构与编程
- STM8S003F3 开发环境搭建
- 为什么要选择Linux
- 华为2019.8.22笔试题
- 主成分分析(PCA)及其可视化——python
- 蒙牛、小米、比亚迪,明星企业为何扎堆换LOGO?
- 首次 LFS 搭建全过程
- pid倒立摆matlab,基于MATLAB的直线一级倒立摆的PID控制研究
- 什么是重载?有什么作用?
- 实验验证二项分布(Binomial)公式正确性
热门文章
- DllMain函数详解
- 44.1KHz的PCM数据转为8K的PCM
- Web自动化测试(一)—— Web自动化入门
- html中怎么只加一条边框,html中如何让层只显示上下边框.急
- Java读取txt文件中格式化信息,显示到JTree中
- 记账方法以及工具推荐,带你走向暴富之路
- 成功解决git clone提示fatal: repository ‘xxx.git/‘ not found
- VScode - 打字炫酷特效震动光效插件(Power Mode)
- SQL 如何在已分组统计的数据中统计某一字段特定值的总数
- Socket通信——同步通信