30.3 数据分析
在本项目中,数据分析工作通过结合股票市场数据和新闻标题,构建了一个基于情感分析的交易策略。首先,从Yahoo Finance获取了多只股票的历史收盘价,然后提取了相关的新闻标题和股票代码。接着,通过计算事件收益,分析新闻发布对股票价格的影响,最终将所有数据合并,以便后续进行情感评估和交易策略的建立。
30.3.1 构建环境
下面代码用于加载必要的库和模块,为文本情感分析与机器学习模型构建准备环境。它包括数据处理、情感分析、模型训练和结果可视化的工具。代码还定义了一个函数 show_panel,用于在Panel界面上展示数据框的前几行。
import spacy
import en_core_web_lg
import pandas as pd
import numpy as np
import nltk
import plotly.express as px
import matplotlib.pyplot as plt
from textblob import TextBlob
from nltk.sentiment.vader import SentimentIntensityAnalyzer
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, confusion_matrix
from sklearn.model_selection import train_test_split, KFold, cross_val_score, GridSearchCV
from keras.preprocessing.text import Tokenizer
from tensorflow.keras.utils import pad_sequences
from keras.models import Sequential
from keras.layers import Dense, Flatten, LSTM, Dropout, Activation, Embedding
nltk.download('vader_lexicon')
import panel as pn
import warnings; warnings.filterwarnings('ignore')
def show_panel(df, top=20):
return pn.widgets.Tabulator(df.head(top),
show_index=False,
pagination='local',
page_size=10)
pn.extension('tabulator')
pn.widgets.Tabulator.theme = 'bootstrap'
30.3.2 获取股票数据
(1)下面代码用于从Yahoo Finance获取指定股票(如AAPL、MSFT等)在2010年至2018年间的历史数据,并将所有股票的收盘数据合并到一个数据框中,最后将其保存为CSV文件。
import yfinance as yf
# 定义要获取数据的股票代码
tickers = ['AAPL','MSFT','AMZN','GOOG',
'AMD','NVDA','TSLA','YELP',
'NFLX','ADBE','BA','AIG']
# 定义数据的起始和结束日期
start = '2010-01-01'
end = '2018-12-31'
df_ticker_return = pd.DataFrame() # 创建一个空的数据框来存储股票数据
# 循环遍历每个股票代码
for ticker in tickers:
ticker_yf = yf.Ticker(ticker) # 获取指定股票的Ticker对象
if df_ticker_return.empty: # 如果数据框为空
df_ticker_return = ticker_yf.history(start=start, end=end) # 获取历史数据
df_ticker_return['ticker'] = ticker # 添加股票代码列
else:
data_temp = ticker_yf.history(start=start, end=end) # 获取历史数据
data_temp['ticker'] = ticker # 添加股票代码列
df_ticker_return = df_ticker_return.append(data_temp) # 将数据追加到数据框中
# 替换股票代码中的'META'为'FB'(如有需要)
df_ticker_return['ticker'] = df_ticker_return['ticker'].replace({'META':'FB'})
print(df_ticker_return['ticker'].unique()) # 打印唯一的股票代码
# 将合并后的数据保存为CSV文件
df_ticker_return.to_csv(r'return_data.csv')
执行后输出:
['AAPL' 'MSFT' 'AMZN' 'GOOG' 'AMD' 'NVDA' 'TSLA' 'YELP' 'NFLX' 'ADBE' 'BA''AIG']
(2)下面代码用于从CSV文件中读取股票历史数据,并将日期列设置为数据框的索引,随后显示数据框的前几行。
# 我们可以预读数据
df_ticker_return = pd.read_csv('return_data.csv',
index_col='Date') # 读取CSV文件,将'Date'列设为索引
df_ticker_return.head() # 显示数据框的前几行
执行后输出:
Open High Low Close Volume Dividends Stock Splits ticker
Date
2010-01-04 00:00:00-05:00 6.437013 6.469284 6.405345 6.454506 493729600 0.0 0.0 AAPL
2010-01-05 00:00:00-05:00 6.472301 6.502159 6.431585 6.465666 601904800 0.0 0.0 AAPL
2010-01-06 00:00:00-05:00 6.465664 6.491300 6.356184 6.362819 552160000 0.0 0.0 AAPL
2010-01-07 00:00:00-05:00 6.386344 6.393884 6.304912 6.351057 477131200 0.0 0.0 AAPL
2010-01-08 00:00:00-05:00 6.342610 6.393882 6.305213 6.393279 447610800 0.0 0.0 AAPL
(3)下面代码用于提取数据框中唯一的股票代码,并将其转换为一个列表,最后打印出这个列表。
# 提取数据框中唯一的股票代码并转换为列表
tickers = list(df_ticker_return['ticker'].unique())
print(tickers) # 打印股票代码列表
执行后输出:
['AAPL', 'MSFT', 'AMZN', 'GOOG', 'AMD', 'NVDA', 'TSLA', 'YELP', 'NFLX', 'ADBE', 'BA', 'AIG']
(4)下面代码用于使用Plotly库绘制股票收盘价的折线图,按股票代码进行分面显示,图表高度设置为900像素,使用白色模板。线条颜色为绿色,宽度为2。最后,通过iframe渲染显示图表。
# 使用Plotly绘制股票收盘价的折线图,按股票代码分面显示
fig = px.line(df_ticker_return, y='Close', facet_col='ticker',
facet_col_wrap=3, height=900, template='plotly_white')
# 更新线条颜色和宽度
fig.update_traces(line_color='#CDE10F', line_width=2)
# 通过iframe渲染显示图表
fig.show(renderer='iframe')
执行效果如图30-1所示。
图30-1 股票收盘价折线图
30.3.3 获取新闻舆情数据
在本项目中,将仅使用从RSS源获取的标题数据。数据存储为JSON格式,需要进行解析以提取文本数据。
(1)下面代码用于从压缩文件中读取新闻标题数据,将其解压并解析为JSON格式,提取特定范围内的文本数据样本。
import zipfile
import json
# 打开压缩文件
z = zipfile.ZipFile("input/news-trading/headlines_archive", "r")
# 获取压缩文件中的第11个文件名
testFile = z.namelist()[10]
# 读取该文件的数据
fileData = z.open(testFile).read()
# 解析JSON数据,提取从第二个到第五百个内容
fileDataSample = json.loads(fileData)['content'][1:500]
# 显示JSON格式的数据样本
fileDataSample
执行后输出:
'li class="n-box-item date-title" data-end="1305172799" data-start="1305086400" data-txt="Tuesday, December 17, 2019">Wednesday, May 11, 2011</li><li class="n-box-item sa-box-item" data-id="76179" data-ts="1305149244"><div class="media media-overflow-fix"><div class="media-left"><a class="box-ticker" href="/symbol/CSCO" target="_blank">CSCO</a></div><div class="media-body"><h4 class="media-heading"><a href="/news/76179" sasource="on_the_move_news_fidelity" target="_blank">Cisco (NASDAQ:CSCO): Pr'
(2)下面代码用于从压缩文件中提取新闻标题数据,解析JSON格式内容,提取股票代码和对应的标题,并将这些数据存储到一个数据框中。最后,过滤掉股票代码为空的行。
from lxml import etree
from io import StringIO
from datetime import date
from tqdm.notebook import tqdm
# 辅助函数用于解析JSON数据
def jsonParser(json_data):
xml_data = json_data['content']
# 解析HTML内容
tree = etree.parse(StringIO(xml_data), parser=etree.HTMLParser())
# 提取新闻标题
headlines = tree.xpath("//h4[contains(@class, 'media-heading')]/a/text()")
assert len(headlines) == json_data['count'] # 确保标题数量正确
# 提取主要股票代码
main_tickers = list(map(lambda x: x.replace('/symbol/', ''), tree.xpath("//div[contains(@class, 'media-left')]//a/@href")))
assert len(main_tickers) == json_data['count'] # 确保股票代码数量正确
# 提取最终标题
final_headlines = [''.join(f.xpath('.//text()')) for f in tree.xpath("//div[contains(@class, 'media-body')]/ul/li[1]")]
# 如果没有标题,则尝试另一种提取方式
if len(final_headlines) == 0:
final_headlines = [''.join(f.xpath('.//text()')) for f in tree.xpath("//div[contains(@class, 'media-body')]")]
final_headlines = [f.replace(h, '').split('\xa0')[0].strip() for f, h in zip(final_headlines, headlines)]
return main_tickers, final_headlines
data = None
data_df_news = []
with zipfile.ZipFile("/kaggle/input/news-trading/headlines_archive", "r") as z:
# 遍历压缩文件中的每个文件
for filename in tqdm(z.namelist()):
try:
with z.open(filename) as f:
data = f.read()
json_data = json.loads(data)
if json_data.get('count', 0) > 10:
# 第一步:解析新闻JSON
main_tickers, final_headlines = jsonParser(json_data)
if len(final_headlines) != json_data['count']:
continue
# 第二步:准备未来收益和事件收益,并为每个股票代码分配收益
file_date = filename.split('/')[-1].replace('.json', '')
file_date = date(int(file_date[:4]), int(file_date[5:7]), int(file_date[8:]))
# 第三步:将所有数据合并到数据框中
df_dict = {'ticker': main_tickers,
'headline': final_headlines,
'date': [file_date] * len(main_tickers)}
df_f = pd.DataFrame(df_dict)
data_df_news.append(df_f)
except:
pass
# 合并所有数据框
data_df_news = pd.concat(data_df_news)
display(data_df_news.head()) # 显示数据框的前几行
print(data_df_news.shape) # 打印数据框的形状
# 选择所有非空的股票代码
data_df_news = data_df_news[~(data_df_news['ticker'] == '')]
data_df_news.shape # 打印筛选后的数据框形状
执行后会输出:
(3)下面代码用于从数据框中筛选出所有非空的股票代码,并打印筛选后的数据框的形状。
# 选择所有非空的股票代码
data_df_news = data_df_news[~(data_df_news['ticker'] == '')]
data_df_news.shape # 打印筛选后的数据框形状
执行后会输出:
(110711, 3)
(4)下面这段代码用于绘制一个条形图,显示前100个股票代码的出现频率,使用Plotly库的“plotly_white”模板进行样式设置。
# 绘制前100个股票代码的出现频率条形图
px.bar(data_df_news['ticker'].value_counts()[:100], template="plotly_white")
执行效果如图30-2所示。
图30-2 前100个股票代码的出现频率条形图
在上面解析的新闻标题数据中,最常见的100个唯一股票代码的数量范围从524到112。
30.3.4 事件收益
在本项目中,我们将以“事件收益”作为目标变量,这指的是与市场事件相关的收益。事件的影响可能会在发布之前、发布时以及事件或新闻发布之后的不同时间生效(例如:t-1表示事件前一天,t表示事件当天,t+1表示事件后一天)。为了确保研究的准确性,我们选择使用一个较宽的时间窗口范围[t、t+1],而不是仅关注事件发生的瞬间t。
下面代码用于计算股票的当前收益和事件收益,将其存储在数据框中,并将日期格式化为日期对象。
# 计算当前收益
df_ticker_return['ret_curr'] = df_ticker_return['Close'].pct_change()
# 计算事件收益
# rtm1 = df_ticker_return['ret_curr'].shift(-1) # 事件前一天的收益
rtp1 = df_ticker_return['ret_curr'].shift(1) # 事件后一天的收益
rt = df_ticker_return['ret_curr'] # 事件当天的收益
# 事件收益等于事件当天收益和事件后一天收益之和
df_ticker_return['eventRet'] = rt + rtp1
# 重置索引
df_ticker_return.reset_index(level=0, inplace=True)
# 将日期格式化为日期对象
df_ticker_return['date'] = pd.to_datetime(df_ticker_return['Date']).apply(lambda x: x.date())
30.3.5 合并数据
“合并数据”指的是将两个数据框(dataframe)结合在一起,以便能够在一个统一的数据集中进行分析。在这里,“merge both of the dataframe together”意思是将包含股票收益的数据框和包含新闻标题的数据框结合起来,以便可以根据日期和股票代码进行进一步分析。
(1)下面代码用于将新闻数据和股票收益数据按日期和股票代码合并,筛选出有效的记录,并最终将合并后的数据保存为CSV文件。
# 按日期和股票代码合并数据框
df_all = pd.merge(data_df_news, df_ticker_return,
how='left',
left_on=['date', 'ticker'],
right_on=['date', 'ticker'])
# 筛选出在指定股票列表中的股票
df_all = df_all[df_all['ticker'].isin(tickers)]
# 选择需要的列
df_all = df_all[['ticker', 'headline', 'date', 'eventRet', 'Close']]
# 删除缺失值
df_all = df_all.dropna()
# 重置索引
df_all = df_all.reset_index(drop=True)
# 将合并后的数据保存为CSV文件
df_all.to_csv('combined.csv', index=False)
(2)下面代码用于读取之前保存的合并数据框 combined.csv,并删除其中的缺失值,然后显示数据的前几行。
# 预读取合并后的数据框
data_df = pd.read_csv('combined.csv')
# 删除缺失值
data_df = data_df.dropna()
# 显示数据框的前几行
data_df.head()
执行后会输出:
ticker headline date eventRet Close
0 AMZN Whole Foods (WFMI) -5.2% following a downgrade... 2011-05-02 0.031269 10.059500
1 NFLX Netflix (NFLX +1.1%) shares post early gains a... 2011-05-02 0.012173 33.884285
2 MSFT The likely winners in Microsoft's (MSFT -1.4%)... 2011-05-10 -0.007741 19.884457
3 MSFT Microsoft (MSFT -1.2%) and Skype signed their ... 2011-05-10 -0.007741 19.884457
5 AMZN Amazon.com (AMZN -1.7%) shares slip as comment... 2011-05-12 0.010426 10.303500
(3)下面代码用于输出合并数据框的形状(行数和列数)以及唯一股票代码的数量。
# 打印数据框的形状(行数和列数)以及唯一股票代码的数量
print(data_df.shape, data_df.ticker.unique().shape)
执行后会输出:
(3142, 5) (12,)
标签:NLP,return,数据,df,data,30,舆情,import,ticker
From: https://blog.csdn.net/asd343442/article/details/143212495