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. 研报复现系列(一):【方正证券】跟踪聪明钱:从分钟行情数据到选股因子

    1.研报概述 本文是研报复现系列的第一篇,文本复现了[方正证券]的研报[跟踪聪明钱:从分钟行情数据到选股因子]. 该研报尝试从分钟行情数据中挖掘出那些聪明人(即机构)所做的交易,称为"聪明钱 ...

  2. 研报复现系列(二):【光大证券】基于阻力支撑相对强度(RSRS)的市场择时

    1.研报概述 本文是券商金工研报复现系列的第二篇,文本复现了[光大证券]的[基于阻力支撑相对强度(RSRS)的市场择时]. 阻力位与支撑位传统的应用方法一般是选取特定的阻力位.支撑位作为阈值来进行突破 ...

  3. 研报复现系列(五)【光大证券】放量恰是入市时:成交量择时初探

    前言 我们是国内普通高校的在校学生,同时也是量化投资的初学者.我们的学校不是清北复交,也没有金融工程实验室,同时地处三线小城,因此我们在校期间较难获得量化实习机会,但我们期待与业界进行沟通.交流. 蔡 ...

  4. 精英任务 | 第二期券商研报复现挑战赛

    你是否不知道该如何在量化投资的路上继续向前? 你是否还在苦于没有研究的思路和方向? 你是否还在纠结几千份研报中,到底该去看哪一个? 研报复现第二期来啦,快来参加吧~ 我们从大量的研报中筛选出有意思的研 ...

  5. 精英任务 | 券商研报复现挑战赛

    你是否不知道该如何在量化投资的路上继续向前? 你是否还在苦于没有研究的思路和方向? 你是否还在纠结几千份研报中,到底该去看哪一个? 来参加聚宽的精英任务吧~ 我们从大量的研报中筛选出了有意思的内容,来 ...

  6. 券商研报复现挑战赛|回望来时路,砥砺再出发

    第一期的研报复现大赛已经落下帷幕啦~ 再次感谢朋友们的踊跃参与和大力支持~ 所有的匍匐都只是高高跃起前的热身, 第二期的赛事即将开启,我们期待您的参与! 第一期研报复现结果回顾 第一期研报复现比赛已经 ...

  7. 【每周研报复现】基于阻力支撑相对强度(RSRS)的市场择时

    原创文章第106篇,专注"个人成长与财富自由.世界运作的逻辑, AI量化投资". 今天要复现的研报是:"光大证券_金融工程深度:基于阻力支撑相对强度(RSRS)的市场择时 ...

  8. Vitu研报复现挑战,来了解一下

    传统金融里,券商每年都会产生大量有价值的研报,互联网的普及更是让我们每一个人都可以阅读到其内容. 在做投资决策前,我们需要信任这些研究,那么我们必须能够复现它.所以Vitu.AI正在通过可复现性挑战鼓 ...

  9. 阻力支撑指标RSRS策略:光大证券研报复现

    昨天我们已经计算好了RSRS指标策略,今天把光大证券的研报复现一下. 由于计算比较耗时,我们会把计算的中间结果的dataframe保存下来. 我们使用hdf5保留数据结果,这里有一个小tip,有点奇怪 ...

最新文章

  1. Exploring Data with Python免费电子书
  2. php多规格多价格,不同规格的不同价格是怎么实现的呢?? 看这里
  3. 二叉树的链式结构的非递归遍历
  4. 情感分析朴素贝叶斯_朴素贝叶斯推文的情感分析
  5. python找人_python之找最后一个人
  6. Java学习-Overload和Override的区别
  7. Python 做自动化测试环境搭建
  8. 大数据智能营销有什么优势
  9. vue click.stop阻止点击事件继续传播
  10. 在linux中安装Jupyter notebook
  11. 电子书 杜春雷 ARM体系结构与编程
  12. STM8S003F3 开发环境搭建
  13. 为什么要选择Linux
  14. 华为2019.8.22笔试题
  15. 主成分分析(PCA)及其可视化——python
  16. 蒙牛、小米、比亚迪,明星企业为何扎堆换LOGO?
  17. 首次 LFS 搭建全过程
  18. pid倒立摆matlab,基于MATLAB的直线一级倒立摆的PID控制研究
  19. 什么是重载?有什么作用?
  20. 实验验证二项分布(Binomial)公式正确性

热门文章

  1. DllMain函数详解
  2. 44.1KHz的PCM数据转为8K的PCM
  3. Web自动化测试(一)—— Web自动化入门
  4. html中怎么只加一条边框,html中如何让层只显示上下边框.急
  5. Java读取txt文件中格式化信息,显示到JTree中
  6. 记账方法以及工具推荐,带你走向暴富之路
  7. 成功解决git clone提示fatal: repository ‘xxx.git/‘ not found
  8. VScode - 打字炫酷特效震动光效插件(Power Mode)
  9. SQL 如何在已分组统计的数据中统计某一字段特定值的总数
  10. Socket通信——同步通信