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

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

上一篇:node+vue实现微信支付(沙箱)完整版,亲测可用


下一篇:支付宝即时到账接口开发 - DEMO讲解