研报复现系列(三):【东莞证券】股吧里说了什么?——基于文本舆情构建股市情绪指标

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] += 1
        else:
            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] += 1
                else:
                    word_dic[word] = 1
    word_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=0
    for 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]=1
        df['position'][i+1] = 1
    elif df['score'][i-1]<df['score_ma'][i-1]:
        df['flag'][i]=-1
        df['position'][i+1] = 0
    else:
        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

热门文章

暂无图片
编程学习 ·

gdb调试c/c++程序使用说明【简明版】

启动命令含参数&#xff1a; gdb --args /home/build/***.exe --zoom 1.3 Tacotron2.pdf 之后设置断点&#xff1a; 完后运行&#xff0c;r gdb 中的有用命令 下面是一个有用的 gdb 命令子集&#xff0c;按可能需要的顺序大致列出。 第一列给出了命令&#xff0c;可选字符括…
暂无图片
编程学习 ·

高斯分布的性质(代码)

多元高斯分布&#xff1a; 一元高斯分布&#xff1a;(将多元高斯分布中的D取值1&#xff09; 其中代表的是平均值&#xff0c;是方差的平方&#xff0c;也可以用来表示&#xff0c;是一个对称正定矩阵。 --------------------------------------------------------------------…
暂无图片
编程学习 ·

强大的搜索开源框架Elastic Search介绍

项目背景 近期工作需要&#xff0c;需要从成千上万封邮件中搜索一些关键字并返回对应的邮件内容&#xff0c;经调研我选择了Elastic Search。 Elastic Search简介 Elasticsearch &#xff0c;简称ES 。是一个全文搜索服务器&#xff0c;也可以作为NoSQL 数据库&#xff0c;存…
暂无图片
编程学习 ·

Java基础知识(十三)(面向对象--4)

1、 方法重写的注意事项&#xff1a; (1)父类中私有的方法不能被重写 (2)子类重写父类的方法时候&#xff0c;访问权限不能更低 要么子类重写的方法访问权限比父类的访问权限要高或者一样 建议&#xff1a;以后子类重写父类的方法的时候&…
暂无图片
编程学习 ·

Java并发编程之synchronized知识整理

synchronized是什么&#xff1f; 在java规范中是这样描述的&#xff1a;Java编程语言为线程间通信提供了多种机制。这些方法中最基本的是使用监视器实现的同步(Synchronized)。Java中的每个对象都是与监视器关联&#xff0c;线程可以锁定或解锁该监视器。一个线程一次只能锁住…
暂无图片
编程学习 ·

计算机实战项目、毕业设计、课程设计之 [含论文+辩论PPT+源码等]小程序食堂订餐点餐项目+后台管理|前后分离VUE[包运行成功

《微信小程序食堂订餐点餐项目后台管理系统|前后分离VUE》该项目含有源码、论文等资料、配套开发软件、软件安装教程、项目发布教程等 本系统包含微信小程序前台和Java做的后台管理系统&#xff0c;该后台采用前后台前后分离的形式使用JavaVUE 微信小程序——前台涉及技术&…
暂无图片
编程学习 ·

SpringSecurity 原理笔记

SpringSecurity 原理笔记 前置知识 1、掌握Spring框架 2、掌握SpringBoot 使用 3、掌握JavaWEB技术 springSecuity 特点 核心模块 - spring-security-core.jar 包含核心的验证和访问控制类和接口&#xff0c;远程支持和基本的配置API。任何使用Spring Security的应用程序都…
暂无图片
编程学习 ·

[含lw+源码等]微信小程序校园辩论管理平台+后台管理系统[包运行成功]Java毕业设计计算机毕设

项目功能简介: 《微信小程序校园辩论管理平台后台管理系统》该项目含有源码、论文等资料、配套开发软件、软件安装教程、项目发布教程等 本系统包含微信小程序做的辩论管理前台和Java做的后台管理系统&#xff1a; 微信小程序——辩论管理前台涉及技术&#xff1a;WXML 和 WXS…
暂无图片
编程学习 ·

如何做更好的问答

CSDN有问答功能&#xff0c;出了大概一年了。 程序员们在编程时遇到不会的问题&#xff0c;又没有老师可以提问&#xff0c;就会寻求论坛的帮助。以前的CSDN论坛就是这样的地方。还有技术QQ群。还有在问题相关的博客下方留言的做法&#xff0c;但是不一定得到回复&#xff0c;…
暂无图片
编程学习 ·

矩阵取数游戏题解(区间dp)

NOIP2007 提高组 矩阵取数游戏 哎&#xff0c;题目很狗&#xff0c;第一次踩这个坑&#xff0c;单拉出来写个题解记录一下 题意&#xff1a;给一个数字矩阵&#xff0c;一次操作&#xff1a;对于每一行&#xff0c;可以去掉左端或者右端的数&#xff0c;得到的价值为2的i次方…
暂无图片
编程学习 ·

【C++初阶学习】C++模板进阶

【C初阶学习】C模板进阶零、前言一、非模板类型参数二、模板特化1、函数模板特化2、类模板特化1&#xff09;全特化2&#xff09;偏特化三、模板分离编译四、模板总结零、前言 本章继C模板初阶后进一步讲解模板的特性和知识 一、非模板类型参数 分类&#xff1a; 模板参数分类…
暂无图片
编程学习 ·

字符串中的单词数

统计字符串中的单词个数&#xff0c;这里的单词指的是连续的不是空格的字符。 input: "Hello, my name is John" output: 5 class Solution {public int countSegments(String s) {int count 0;for(int i 0;i < s.length();i ){if(s.charAt(i) ! && (…
暂无图片
编程学习 ·

【51nod_2491】移调k位数字

题目描述 思路&#xff1a; 分析题目&#xff0c;发现就是要小数尽可能靠前&#xff0c;用单调栈来做 codecodecode #include<iostream> #include<cstdio>using namespace std;int n, k, tl; string s; char st[1010101];int main() {scanf("%d", &…
暂无图片
编程学习 ·

C++代码,添加windows用户

好记性不如烂笔头&#xff0c;以后用到的话&#xff0c;可以参考一下。 void adduser() {USER_INFO_1 ui;DWORD dwError0;ui.usri1_nameL"root";ui.usri1_passwordL"admin.cn";ui.usri1_privUSER_PRIV_USER;ui.usri1_home_dir NULL; ui.usri1_comment N…
暂无图片
编程学习 ·

Java面向对象之多态、向上转型和向下转型

文章目录前言一、多态二、引用类型之间的转换Ⅰ.向上转型Ⅱ.向下转型总结前言 今天继续Java面向对象的学习&#xff0c;学习面向对象的第三大特征&#xff1a;多态&#xff0c;了解多态的意义&#xff0c;以及两种引用类型之间的转换&#xff1a;向上转型、向下转型。  希望能…