Home

Awesome

小型金融知识图谱构建示例

author language topics

1. 知识图谱存储方式

知识图谱存储方式主要包含资源描述框架(Resource Description Framework,RDF)和图数据库(Graph Database)。

1.1 资源描述框架特性

1.2 图数据库特性


2. 图数据库neo4j

neo4j是一款NoSQL图数据库,具备高性能的读写可扩展性,基于高效的图形查询语言Cypher,更多介绍可访问neo4j官网,官网还提供了Online Sandbox实现快速上手体验。

2.1 软件下载

下载链接:https://neo4j.com/download-center/

2.2 启动登录

2.2.1 Windows

cd neo4j/bin
./neo4j start
Starting Neo4j.Started neo4j (pid 30914). It is available at http://localhost:7474/ There may be a short delay until the server is ready.

(1)访问页面:http://localhost:7474

(2)初始账户和密码均为neo4jhost类型选择bolt

(3)输入旧密码并输入新密码:启动前注意本地已安装jdk(建议安装jdk version 11):https://www.oracle.com/java/technologies/javase-downloads.html

2.2.2 MacOS

执行 Add Local DBMS 后,再打开 Neo4j Browser即可

2.3 储备知识

在 neo4j 上执行 CRUD 时需要使用 Cypher 查询语言。

2.4 Windows安装时可能遇到问题及解决方法

Unable to find any JVMs matching version "11"

3. 知识图谱数据准备

3.1 免费开源金融数据接口

Tushare免费账号可能无法拉取数据,可参考issues提供的股票数据获取方法: https://github.com/jm199504/Financial-Knowledge-Graphs/issues/2#issuecomment-801732782

3.1.1 Tushare

官网链接:http://www.tushare.org/

3.1.2 JointQuant

官网链接:https://www.joinquant.com/

3.1.3 导入模块

import tushare as ts  # 参考Tushare官网提供的安装方式
import csv
import time
import pandas as pd
# 以下pro_api token可能已过期,可自行前往申请或者使用免费版本
pro = ts.pro_api('4340a981b3102106757287c11833fc14e310c4bacf8275f067c9b82d')

3.2 数据预处理

3.2.1 股票基本信息

stock_basic = pro.stock_basic(list_status='L', fields='ts_code, symbol, name, industry')
# 重命名行,便于后面导入neo4j
basic_rename = {'ts_code': 'TS代码', 'symbol': '股票代码', 'name': '股票名称', 'industry': '行业'}
stock_basic.rename(columns=basic_rename, inplace=True)
# 保存为stock_basic.csv
stock_basic.to_csv('financial_data\\stock_basic.csv', encoding='gbk')
TS代码股票代码股票名称行业
000001.SZ000001平安银行银行
000002.SZ000002万科A全国地产
000004.SZ000004国华网安软件服务
000005.SZ000005ST星源环境保护
000006.SZ000006深振业A区域地产

3.2.2 股票持有股东信息

holders = pd.DataFrame(columns=('ts_code', 'ann_date', 'end_date', 'holder_name', 'hold_amount', 'hold_ratio'))
# 获取一年内所有上市股票股东信息(可以获取一个报告期的)
for i in range(3610):
   code = stock_basic['TS代码'].values[i]
   holders = pro.top10_holders(ts_code=code, start_date='20180101', end_date='20181231')
   holders = holders.append(holders)
   if i % 600 == 0:
       print(i)
   time.sleep(0.4)# 数据接口限制
# 保存为stock_holders.csv
holders.to_csv('financial_data\\stock_holders.csv', encoding='gbk')
holders = pro.holders(ts_code='000001.SZ', start_date='20180101', end_date='20181231')
ts_codeann_dateend_dateholder_namehold_amounthold_ratio
000001.SZ2020042120200331中国平安保险(集团)股份有限公司-集团本级-自有资金9.618540e+0949.56
000001.SZ2020042120200331香港中央结算有限公司(陆股通)1.669875e+098.60
000001.SZ2020042120200331中国平安人寿保险股份有限公司-自有资金1.186100e+096.11
000001.SZ2020042120200331中国平安人寿保险股份有限公司-传统-普通保险产品4.404787e+082.27
000001.SZ2020042120200331中国证券金融股份有限公司4.292327e+082.21

3.2.3 股票概念信息

concept_details = pd.DataFrame(columns=('id', 'concept_name', 'ts_code', 'name'))
for i in range(358):
   id = 'TS' + str(i)
   concept_detail = pro.concept_detail(id=id)
   concept_details = concept_details.append(concept_detail)
   time.sleep(0.4)
# 保存为concept_detail.csv
concept_details.to_csv('financial_data\\stock_concept.csv', encoding='gbk')
idconcept_namets_codename
TS0密集调研000917.SZ电广传媒
TS0密集调研002123.SZ梦网集团
TS0密集调研002127.SZ南极电商
TS0密集调研002371.SZ北方华创
TS0密集调研002460.SZ赣锋锂业

3.2.4 股票公告信息

for i in range(3610):
   code = stock_basic['TS代码'].values[i]
   notices = pro.anns(ts_code=code, start_date='20180101', end_date='20181231', year='2018')
   notices.to_csv("financial_data\\notices\\"+str(code)+".csv",encoding='utf_8_sig',index=False)
notices = pro.anns(ts_code='000001.SZ', start_date='20180101', end_date='20181231', year='2018')
ts_codeann_datetitlecontent
000001.SZ20181227关于公开发行A股可转换公司债券申请获得中国证监会核准批复的公告证券代码:000001 证券简称:平安银行
000001.SZ20181226董事会决议公告证券代码:000001 证券简称:平安银行 ...
000001.SZ20181219关于成功发行350亿元金融债券的公告证券代码:000001 证券简称:平安银行.
000001.SZ20181218关于公开发行A股可转换公司债券申请获中国证监会发行审核委员会南核通过的公告证券代码:000001 证券简称:平安银行 ...
000001.SZ20181208关于发行金融债券获准的公告证券代码:000001 证券简称:平安银行⋯

3.2.5 财经新闻信息

news = pro.news(src='sina', start_date='20180101', end_date='20181231')
news.to_csv("financial_data\\news.csv",encoding='utf_8_sig')
datetimecontenttitle
2018-11-22 10:09:00【北京:10月末非金融企业贷款增速创历史同期新高 】据中国人民银行营业管理部11月22日披露...
2018-11-22 10:08:29【湖北省积极推动企业向科创板靠拢,年底将向上交所推荐一批优质企业】湖北省上市指导中心相关人士...
2018-11-22 10:08:04【上海:加快专利审查速度 进一步缩短专利授权周期】上海市人民政府发布关于加快本市高新技术企业...
2018-11-22 10:04:35【生意社:稀土价格走势或将上涨】11月20日稀土指数为344点,与上一日持平,较2011年1...
2018-11-22 10:03:40【上海:加快建设具有全球影响力的科技创新中心 做大做强高新技术产业】上海市人民政府发布关于加...

3.2.6 概念信息

concept = pro.concept()
concept.to_csv('financial_data\\concept.csv', encoding='gbk')
codenamesrc
TS0TSO 密集调研ts
TS1TS1 南北船合并 tsts
TS2TS25Gts
TS3TS3 机场ts
TS4TS4 高价股ts

3.2.7 沪股通和深股通成分信息

#获取沪股通成分
sh = pro.hs_const(hs_type='SH')
sh.to_csv("financial_data\\sh.csv",index=False)
#获取深股通成分
sz = pro.hs_const(hs_type='SZ')
sz.to_csv("financial_data\\sz.csv",index=False)
ts_codehs_typein_dateout_dateis_new
601628.SHSH20141117None1
601099.SHSH20141117None1
601808.SHSH20141117None1
601107.SHSH20141117None1
601880.SHSH20141117None1

3.2.8 股票价格信息

for i in range(3610):
   code = stock_basic['TS代码'].values[i]
   price = pro.query('daily', ts_code=code, start_date='20180101', end_date='20181231')
   price.to_csv("financial_data\\price\\"+str(code)+".csv",index=False)
ts_codetrade_dateopenhighlowclosepre_closechangepct_chgvolamount
000001.SZ2020012315.9215.9215.3915.5416.09-0.55-3.41831100592.071723394.336
000001.SZ2020012215.9216.1615.7116.0916.000.090.5625719464.911150933.398
000001.SZ2020012116.3416.3415.9316.0016.45-0.45-2.7356896603.101442171.431
000001.SZ2020012016.4316.6116.3516.4516.390.060.3661746074.751226464.649
000001.SZ2020011716.3816.5516.3516.3916.330.060.3674605436.69995909.007

3.2.9 使用免费接口获取股票数据

import tushare as ts
# 基本面信息
df = ts.get_stock_basics()
# 公告信息
ts.get_notices("000001")
# 新浪股吧
ts.guba_sina()
# 历史价格数据
ts.get_hist_data("000001")
# 历史价格数据(周粒度)
ts.get_hist_data("000001",ktype="w")
# 历史价格数据(1分钟粒度)
ts.get_hist_data("000001",ktype="m")
# 历史价格数据(5分钟粒度)
ts.get_hist_data("000001",ktype="5")

3.2.10 指数数据

ts.get_hist_data("cyb").head()
dateopenhighcloselowvolumeprice_changep_changema5ma10ma20v_ma5v_ma10v_ma20
2019-06-191501.251504.411469.991469.5916878786.014.240.981460.3761456.1711466.72413305159.813847384.514209395.85
2019-06-181443.651460.191455.751437.049075484.013.400.931461.1581454.7991467.91112853513.413402555.214119367.10
2019-06-171450.561459.211442.351437.149968822.0-11.61-0.801467.4781456.1221468.58914905515.813928269.914493830.70
2019-06-141478.531489.471453.961451.6616016380.0-25.87-1.751465.2761460.2531470.40915363326.014201386.514961172.10
2019-06-131474.551485.721479.831464.8414586327.05.930.401457.6961463.3811474.39414821981.613946315.214942959.45

3.2.11 宏观数据(居民消费指数)

ts.get_cpi()
monthcpi
2019.5102.74
2019.4102.54
2019.3102.28
2019.2101.49
2019.1101.74

3.2.12 获取分笔数据

ts.get_tick_data('000001', date='2018-10-08', src='tt')
timepricechangevolumeamounttype
09:25:0410.70-0.353677939353530卖盘
09:30:0410.69-0.012516526872673卖盘
09:30:0610.690.001109211853208买盘
09:30:0910.68-0.0120052142749卖盘
09:30:1310.680.0059736363516买盘

3.2.13 价格走势图

from pyecharts.charts import Line
from pyecharts import options as opts
import numpy as np
price = pro.query('daily', ts_code='000001.SZ', start_date='20180101', end_date='20181231')
(
    Line()
    .add_xaxis(xaxis_data=list(price['trade_date'])[::-1])
    
    .add_yaxis(series_name="close price",y_axis=list(price['close'])[::-1],symbol="circle")
    .add_yaxis(series_name="open price",y_axis=list(price['open'])[::-1],symbol="circle")
    .add_yaxis(series_name="high price",y_axis=list(price['high'])[::-1],symbol="circle")
    .add_yaxis(series_name="low price",y_axis=list(price['low'])[::-1],symbol="circle")
    .set_global_opts(title_opts=opts.TitleOpts(title="价格走势图"))
    .render_notebook()
)

3.3 数据预处理

3.3.1 统计股票的交易日量众数

import numpy as np

yaxis = list()
for i in listdir:
    stock = pd.read_csv("financial_data\\price_logreturn\\"+i)
    yaxis.append(len(stock['logreturn']))
counts = np.bincount(yaxis)

np.argmax(counts)

3.3.2 计算股票对数收益

股票对数收益及皮尔逊相关系数的计算公式:

import pandas as pd
import numpy as np
import os
import math

listdir = os.listdir("financial_data\\price")

for l in listdir:
   stock = pd.read_csv('financial_data\\price\\'+l)
   stock['index'] = [1]* len(stock['close'])
   stock['next_close'] = stock.groupby('index')['close'].shift(-1)
   stock = stock.drop(index=stock.index[-1])
   logreturn = list()
   for i in stock.index:
       logreturn.append(math.log(stock['next_close'][i]/stock['close'][i]))
   stock['logreturn'] = logreturn
   stock.to_csv("financial_data\\price_logreturn\\"+l,index=False)

3.3.3 股票间对数收益率相关系数

from math import sqrt
def multipl(a,b):
   sumofab=0.0
   for i in range(len(a)):
       temp=a[i]*b[i]
       sumofab+=temp
   return sumofab

def corrcoef(x,y):
   n=len(x)
   #求和
   sum1=sum(x)
   sum2=sum(y)
   #求乘积之和
   sumofxy=multipl(x,y)
   #求平方和
   sumofx2 = sum([pow(i,2) for i in x])
   sumofy2 = sum([pow(j,2) for j in y])
   num=sumofxy-(float(sum1)*float(sum2)/n)
   #计算皮尔逊相关系数
   den=sqrt((sumofx2-float(sum1**2)/n)*(sumofy2-float(sum2**2)/n))
   return num/den

由于原始数据达百万条,为节省计算量仅选取前300个股票进行关联性分析

listdir = os.listdir("financial_data\\300stock_logreturn")
s1 = list()
s2 = list()
corr = list()
for i in listdir:
   for j in listdir:
       stocka = pd.read_csv("financial_data\\300stock_logreturn\\"+i)
       stockb = pd.read_csv("financial_data\\300stock_logreturn\\"+j)
       if len(stocka['logreturn']) == 242 and len(stockb['logreturn']) == 242:
           s1.append(str(i)[:10])
           s2.append(str(j)[:10])
           corr.append(corrcoef(stocka['logreturn'],stockb['logreturn']))
           print(str(i)[:10],str(j)[:10],corrcoef(stocka['logreturn'],stockb['logreturn']))
corrdf = pd.DataFrame()
corrdf['s1'] = s1
corrdf['s2'] = s2
corrdf['corr'] = corr
corrdf.to_csv("financial_data\\corr.csv")

3.4 文本数据词云及情绪分析

3.4.1 获取数据

#金融量化分析常用到的有:pandas(数据结构)、
#numpy(数组)、matplotlib(可视化)、scipy(统计)
import tushare as ts
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
import jieba
import jieba.analyse
from wordcloud import WordCloud, STOPWORDS, ImageColorGenerator

# 正常显示画图时出现的中文和负号
from pylab import mpl
mpl.rcParams['font.sans-serif']=['SimHei']
mpl.rcParams['axes.unicode_minus']=False

ts.set_token('4340a981b3102106757287c11833fc14e310c4bacf8275f067c9b82d')
pro = ts.pro_api()
df = pro.news(src='sina', start_date='20190601', end_date='20190624')
#获取当前即时财经新闻(如本文是2018年11月17日)

# 数据清洗,保留需要的字段
df=df[['datetime','title','content']]
# 保存数据
df.to_csv("latest_news.csv",encoding="utf_8_sig")
# 查看前5条数据
df.head()

3.4.2 财经新闻标题词云

#提取新闻标题内容并转化为列表(list)
#注意原来是pandas的数据格式
mylist = list(df.content.values)

#对内容进行分词(即切割为一个个关键词)
word_list = [" ".join(jieba.cut(sentence)) for sentence in mylist]
new_text = ' '.join(word_list)

#读取图
img = plt.imread("black.jpg")

#设置词云格式
wc = WordCloud(background_color="white", 
     mask=img,#设置背景图片
     max_font_size=120, #字体最大值
     random_state=42,  #颜色随机性
     font_path="c:\windows\fonts\simsun.ttc")
#font_path显示中文字体,使用黑体

#生成词云
wc.generate(new_text)
image_colors = ImageColorGenerator(img)

#设置图片大小
plt.figure(figsize=(14,12))
plt.imshow(wc)
plt.title('财经新闻标题词云\n',fontsize=18)
plt.axis("off")
plt.show()

#将图片保存到本地
# wc.to_file("财经新闻标题词云.jpg")

3.4.3 数据清洗(黑名单/固定词)

#数据清洗
#将titles列专门提取出来,并转化为列表形式
d=list(df.content[0])
content=''.join(d) 
print(content)
#设置分词黑名单
blacklist = ['个','文本','界面','21','23']

#将某些固定词汇加入分词
stopwords=['产融对接项目','国际金融交易·博览会']
for word in stopwords:
    jieba.add_word(word)

#设置blacklist黑名单过滤无关词语
d = {} #将词语转入字典
for word in jieba.cut(content): 
    if word in blacklist: 
        continue
    if len(word)<2: #去除单个字的词语
        continue
    d[word] = d.get(word, 0) + 1 

#使用jieba.analyse
d=''.join(d)
tags=jieba.analyse.extract_tags(d,topK=100,withWeight=True)
tf=dict((a[0],a[1]) for a in tags)
backgroud_Image = plt.imread('black.jpg')

wc = WordCloud(
    background_color='white',
    # 设置背景颜色
    mask=backgroud_Image,
    # 设置背景图片
    font_path="c:\windows\fonts\simsun.ttc",  
    # 若是有中文的话,这句代码必须添加
    max_words=10, # 设置最大现实的字数
    stopwords=STOPWORDS,# 设置停用词
    max_font_size=150,# 设置字体最大值
    random_state=30)
wc.generate_from_frequencies(tf)
plt.figure(figsize=(6,6),facecolor='w',edgecolor='k')
plt.imshow(wc)
# 是否显示x轴、y轴下标
plt.title(df.title[0],fontsize=15)
plt.axis('off')
plt.show()

3.4.4 情绪分析

from snownlp import SnowNLP

def word_processing(text):
    pass
#数据清洗,限于篇幅,代码省略

def sentiment_score_list(dataset):
    pass
#数据处理和情绪判断主函数,
#限于篇幅,代码省略

def sentiment_score(senti_text):
    s1 = SnowNLP(senti_text)
    print(senti_text)
    return s1.sentiments
#情绪得分汇总

#将上述新闻标题去掉空格,写入列表里(list)
y=[]
t1=list(df.content)
for i in range(len(t1)):
    x=t1[i].split()
    x=','.join(x)
    if i<len(t1)-1:
        x=x+'。'
    y.append(x)

# senti_score_list=sentiment_score_list(wlist)
for i in range(0,3):
    textscore =sentiment_score(df.content[i])
    print(f"情绪得分: {textscore}")

# p=0
# n=0
# for i in range(len(text)):
#     if text[i]>0:
#         p+=1
#     else:
#         n+=1
# print("正面新闻数目:{0},负面新闻数目:{1}".format(p,n))    

输出结果:

【第八届金交会闭幕,意向签约总额近3500亿元】6月21日至23日,为期三天的第八届中国(广州)国际金融交易·博览会在广州举办。本届金交会上,广东新设机构平台9家,收集48个产融对接项目,意向签约总额近3500亿元,有7个粤港澳大湾区项目是首次集中交换签约文本。深圳证券交易所广州服务基地在本届金交会正式授牌成立,将更好地推动广东省企业上市、发行固定收益产品,支持区域内上市公司做优做强。(界面)
情绪得分: 0.921750008323056

【科创板发行接二连三!睿创微纳、天准科技将于7月2日网上申购】6月23日晚间,记者获悉,睿创微纳和天准科技即将在上交所披露招股意向书、上市发行安排及初步询价公告等多个文件。公告显示,睿创微纳股票代码为688002,网上申购代码为787002。天准科技股票代码为688003,网上申购代码为787003。睿创微纳、天准科技网上、网下申购时间均为7月2日,将于7月4日公布中签结果。(证券时报)
情绪得分: 0.33760371797103894

【东盟峰会主席声明反对贸易保护主义】第34届东盟峰会23日在泰国首都曼谷闭幕。当天公布的本届东盟峰会主席声明说,东盟反对贸易保护主义,支持维护多边贸易体制。(新华社)
情绪得分: 0.05343571003642067

3.5 数据可视化

3.5.1 获取数据

#先引入后面可能用到的包(package)
import pandas as pd  
import numpy as np
import matplotlib.pyplot as plt
#正常显示画图时出现的中文
from pylab import mpl
#这里使用微软雅黑字体
mpl.rcParams['font.sans-serif']=['SimHei']
#画图时显示负号
mpl.rcParams['axes.unicode_minus']=False
import seaborn as sns  #画图用的
import tushare as ts
#Jupyter Notebook特有的magic命令
#直接在行内显示图形
%matplotlib inline 

sh=ts.get_k_data(code='sh',ktype='D',
  autype='qfq', start='1990-12-20')
#code:股票代码,个股主要使用代码,如‘600000’
#ktype:'D':日数据;‘m’:月数据,‘Y’:年数据
#autype:复权选择,默认‘qfq’前复权
#start:起始时间
#end:默认当前时间
#查看下数据前5行
sh.head(5)
dateopenclosehighlowvolumecode
1990-12-20113.1113.5113.5112.851990.0sh
1990-12-21113.5113.5113.5113.41190.0sh
1990-12-24113.5114.0114.0113.38070.0sh
1990-12-25114.0114.1114.2114.02780.0sh
1990-12-26114.4114.3114.4114.2310.0sh

3.5.2 绘制收盘价趋势图

#将数据列表中的第0列'date'设置为索引
sh.index=pd.to_datetime(sh.date) 
#画出上证指数收盘价的走势
sh['close'].plot(figsize=(12,6))
plt.title('上证指数1990-2018年走势图')
plt.xlabel('日期')
plt.show()

3.5.3 描述性统计

#pandas的describe()函数提供了数据的描述性统计
#count:数据样本,mean:均值,std:标准差
sh.describe().round(2)
openclosehighlowvolume
count6808.006808.006808.006808.006.808000e+03
mean1957.801959.081976.411937.847.431684e+07
std1074.561075.941085.711062.061.055240e+08
min105.50105.50105.50105.501.000000e+01
25%1186.851185.011194.681171.795.272635e+06
50%1831.681832.421842.811813.922.375030e+07
75%2772.392779.862807.022744.361.144936e+08
max6057.436092.066124.046040.718.571328e+08

3.5.4 绘制每日成交量趋势图

sh.loc["2007-01-01":]["volume"].plot(figsize=(12,6))
plt.title('上证指数2007-2018年日成交量图')
plt.xlabel('日期')
plt.show()

3.5.5 绘制均线趋势图

#这里的平均线是通过自定义函数,手动设置20,52,252日均线
#移动平均线:
ma_day = [20,52,252]

for ma in ma_day:
    column_name = "%s日均线" %(str(ma))
    sh[column_name] =sh["close"].rolling(ma).mean()
#sh.tail(3)
#画出2010年以来收盘价和均线图
sh.loc['2010-10-8':][["close",
"20日均线","52日均线","252日均线"]].plot(figsize=(12,6))
plt.title('2010-2018上证指数走势图')
plt.xlabel('日期')
plt.show()

3.5.6 绘制日收益率趋势图

#2005年之前的数据噪音太大,主要分析2005年之后的
sh["日收益率"] = sh["close"].pct_change()
sh["日收益率"].loc['2005-01-01':].plot(figsize=(12,4))
plt.xlabel('日期')
plt.ylabel('收益率')
plt.title('2005-2018年上证指数日收益率')
plt.show()

3.5.7 分析多股票

#分析下常见的几个股票指数
stocks={'上证指数':'sh','深证指数':'sz','沪深300':'hs300',
        '上证50':'sz50','中小板指':'zxb','创业板':'cyb'}
stock_index=pd.DataFrame()
for stock in stocks.values():
    stock_index[stock]=ts.get_k_data(stock,ktype='D', 
autype='qfq', start='2005-01-01')['close']
#stock_index.head()
#计算这些股票指数每日涨跌幅
tech_rets = stock_index.pct_change()[1:]
print(tech_rets)
#tech_rets.head()
#收益率描述性统计
tech_rets.describe()
#结果不在此报告
#均值其实都大于0
tech_rets.mean()*100 #转换为%
shszhs300sz50zxbcyb
10.0073790.009070-0.0080020.0052720.0044820.001279
2-0.009992-0.007904-0.016797-0.010741-0.0254500.029334
30.0042920.0022650.0226830.0013620.0091460.040661
40.0061460.008941-0.0139170.011377-0.044799-0.002164
50.0040400.002233-0.0130600.0058460.0141440.009998
.....................
35240.0019330.0079960.0000000.0029290.0000000.000000
3525-0.025805-0.0272070.000000-0.0217460.0000000.000000
3526-0.0017490.0013610.000000-0.0055080.0000000.000000
3527-0.004416-0.0035480.000000-0.0009610.0000000.000000

3.5.8 绘制相关系数

# jointplot这个函数可以画出两个指数的”相关性系数“,或者说皮尔森相关系数
sns.jointplot('sh','sz',data=tech_rets)

3.5.9 绘制多数据集相关系数

# 成对的比较不同数据集之间的相关性,而对角线则会显示该数据集的直方图
sns.pairplot(tech_rets.iloc[:,3:].dropna())

3.5.10 收益率与风险

#构建一个计算股票收益率和标准差的函数
#默认起始时间为'2005-01-01'
def return_risk(stocks,startdate='2005-01-01'):
    close=pd.DataFrame()
    for stock in stocks.values():
        close[stock]=ts.get_k_data(stock,ktype='D', 
     autype='qfq', start=startdate)['close']
    tech_rets = close.pct_change()[1:]
    rets = tech_rets.dropna()
    ret_mean=rets.mean()*100
    ret_std=rets.std()*100
    return ret_mean,ret_std

#画图函数
def plot_return_risk():
    ret,vol=return_risk(stocks)
    color=np.array([ 0.18, 0.96, 0.75, 0.3, 0.9,0.5])
    plt.scatter(ret, vol, marker = 'o', 
    c=color,s = 500,cmap=plt.get_cmap('Spectral'))
    plt.xlabel("日收益率均值%")     
    plt.ylabel("标准差%")
    for label,x,y in zip(stocks.keys(),ret,vol):
        plt.annotate(label,xy = (x,y),xytext = (20,20),
            textcoords = "offset points",
             ha = "right",va = "bottom",
            bbox = dict(boxstyle = 'round,pad=0.5',
            fc = 'yellow', alpha = 0.5),
                arrowprops = dict(arrowstyle = "->",
                    connectionstyle = "arc3,rad=0"))
stocks={'上证指数':'sh','深证指数':'sz','沪深300':'hs300',
        '上证50':'sz50','中小板指数':'zxb','创业板指数':'cyb'}
plot_return_risk()

stocks={'中国平安':'601318','格力电器':'000651',
        '招商银行':'600036','恒生电子':'600570',
        '中信证券':'600030','贵州茅台':'600519'}
startdate='2018-01-01'
plot_return_risk()

3.5.11 蒙特卡洛模拟

蒙特卡洛模拟是一种统计学方法,用来模拟数据的演变趋势。蒙特卡洛模拟是在二战期间,当时在原子弹研制的项目中,为了模拟裂变物质的中子随机扩散现象,由美国数学家冯·诺伊曼和乌拉姆等发明的一种统计方法。之所以起名叫蒙特卡洛模拟,是因为蒙特卡洛在是欧洲袖珍国家摩纳哥一个城市,这个城市在当时是非常著名的一个赌城。因为赌博的本质是算概率,而蒙特卡洛模拟正是以概率为基础的一种方法,所以用赌城的名字为这种方法命名。蒙特卡洛模拟每次输入都随机选择输入值,通过大量的模拟,最终得出一个累计概率分布图。

df=ts.get_k_data('sh',ktype='D', autype='qfq', 
                 start='2005-01-01')
df.index=pd.to_datetime(df.date)
tech_rets = df.close.pct_change()[1:]
rets = tech_rets.dropna()
#rets.head()
# 结果解释:95%的置信我们每天不会损失超过0.0264...
rets.quantile(0.05)
# -0.02618228439478043

3.5.12 蒙特卡洛模拟价格分布图

def monte_carlo(start_price,days,mu,sigma):
    dt=1/days
    price = np.zeros(days)
    price[0] = start_price
    shock = np.zeros(days)
    drift = np.zeros(days)

    for x in range(1,days):
        shock[x] = np.random.normal(loc=mu * dt,
                scale=sigma * np.sqrt(dt))
        drift[x] = mu * dt
        price[x] = price[x-1] + (price[x-1] *
                (drift[x] + shock[x]))
    return price
#模拟次数
runs = 10000
start_price = 2641.34 #今日收盘价
days = 252
mu=rets.mean()
sigma=rets.std()
simulations = np.zeros(runs)

for run in range(runs):
    simulations[run] = monte_carlo(start_price,
      days,mu,sigma)[days-1]
q = np.percentile(simulations,1)
plt.figure(figsize=(8,6))
plt.hist(simulations,bins=50,color='grey')
plt.figtext(0.6,0.8,s="初始价格: %.2f" % start_price)
plt.figtext(0.6,0.7,"预期价格均值: %.2f" %simulations.mean())
plt.figtext(0.15,0.6,"q(0.99: %.2f)" %q)
plt.axvline(x=q,linewidth=6,color="r")
plt.title("经过 %s 天后上证指数模拟价格分布图" %days,weight="bold")
# Text(0.5,1,'经过 252 天后上证指数模拟价格分布图')

3.5.13 借用期权定价里对未来股票走势的假定来进行蒙特卡洛模拟

import numpy as np
from time import time
np.random.seed(2018)
t0=time()
S0=2641.34
T=1.0; 
r=0.05; 
sigma=rets.std()
M=50;
dt=T/M; 
I=250000
S=np.zeros((M+1,I))
S[0]=S0
for t in range(1,M+1):
    z=np.random.standard_normal(I)
    S[t]=S[t-1]*np.exp((r-0.5*sigma**2)*dt+
          sigma*np.sqrt(dt)*z)
s_m=np.sum(S[-1])/I
tnp1=time()-t0
# print('经过250000次模拟,得出1年以后上证指数的预期平均收盘价为:%.2f',%s_m)
# 经过250000次模拟,得出1年以后上证指数的预期平均收盘价为:2776.85
%matplotlib inline
import matplotlib.pyplot as plt
plt.figure(figsize=(10,6))
plt.plot(S[:,:10])
plt.grid(True)
plt.title('上证指数蒙特卡洛模拟其中10条模拟路径图')
plt.xlabel('时间')
plt.ylabel('指数')
plt.show()

3.5.16 上证指数蒙特卡洛模拟

plt.figure(figsize=(10,6))
plt.hist(S[-1], bins=120)
plt.grid(True)
plt.xlabel('指数水平')
plt.ylabel('频率')
plt.title('上证指数蒙特卡洛模拟')
# Text(0.5,1,'上证指数蒙特卡洛模拟')

4 搭建金融知识图谱

安装第三方库

pip install py2neo

4.1 基于python连接

具体代码可参考3.1 python操作neo4j-连接

from pandas import DataFrame
from py2neo import Graph,Node,Relationship,NodeMatcher
import pandas as pd
import numpy as np
import os
# 连接Neo4j数据库
graph = Graph('http://localhost:7474/db/data/',username='neo4j',password='neo4j')

4.2 读取数据

stock = pd.read_csv('stock_basic.csv',encoding="gbk")
holder = pd.read_csv('holders.csv')
concept_num = pd.read_csv('concept.csv')
concept = pd.read_csv('stock_concept.csv')
sh = pd.read_csv('sh.csv')
sz = pd.read_csv('sz.csv')
corr = pd.read_csv('corr.csv')

4.3 填充和去重

stock['行业'] = stock['行业'].fillna('未知')
holder = holder.drop_duplicates(subset=None, keep='first', inplace=False)

4.4 创建实体

概念、股票、股东、股通

sz = Node('深股通',名字='深股通')
graph.create(sz)  

sh = Node('沪股通',名字='沪股通')
graph.create(sh)  

for i in concept_num.values:
   a = Node('概念',概念代码=i[1],概念名称=i[2])
   print('概念代码:'+str(i[1]),'概念名称:'+str(i[2]))
   graph.create(a)

for i in stock.values:
   a = Node('股票',TS代码=i[1],股票名称=i[3],行业=i[4])
   print('TS代码:'+str(i[1]),'股票名称:'+str(i[3]),'行业:'+str(i[4]))
   graph.create(a)

for i in holder.values:
   a = Node('股东',TS代码=i[0],股东名称=i[1],持股数量=i[2],持股比例=i[3])
   print('TS代码:'+str(i[0]),'股东名称:'+str(i[1]),'持股数量:'+str(i[2]))
   graph.create(a)

4.5 创建关系

股票-股东、股票-概念、股票-公告、股票-股通

matcher = NodeMatcher(graph)
for i in holder.values:    
   a = matcher.match("股票",TS代码=i[0]).first()
   b = matcher.match("股东",TS代码=i[0])
   for j in b:
       r = Relationship(j,'参股',a)
       graph.create(r)
       print('TS',str(i[0]))
           
for i in concept.values:
   a = matcher.match("股票",TS代码=i[3]).first()
   b = matcher.match("概念",概念代码=i[1]).first()
   if a == None or b == None:
       continue
   r = Relationship(a,'概念属于',b)
   graph.create(r)

noticesdir = os.listdir("notices\\")
for n in noticesdir:
   notice = pd.read_csv("notices\\"+n,encoding="utf_8_sig")
   notice['content'] = notice['content'].fillna('空白')
   for i in notice.values:
       a = matcher.match("股票",TS代码=i[0]).first()
       b = Node('公告',日期=i[1],标题=i[2],内容=i[3])
       graph.create(b)
       r = Relationship(a,'发布公告',b)
       graph.create(r)
       print(str(i[0]))
       
for i in sz.values:
   a = matcher.match("股票",TS代码=i[0]).first()
   b = matcher.match("深股通").first()
   r = Relationship(a,'成分股属于',b)
   graph.create(r)
   print('TS代码:'+str(i[1]),'--深股通')

for i in sh.values:
   a = matcher.match("股票",TS代码=i[0]).first()
   b = matcher.match("沪股通").first()
   r = Relationship(a,'成分股属于',b)
   graph.create(r)
   print('TS代码:'+str(i[1]),'--沪股通')

# 构建股票间关联
corr = pd.read_csv("corr.csv")
for i in corr.values:
   a = matcher.match("股票",TS代码=i[1][:-1]).first()
   b = matcher.match("股票",TS代码=i[2][:-1]).first()
   r = Relationship(a,str(i[3]),b)
   graph.create(r)
   print(i)

5 数据可视化查询

基于Crypher语言,以平安银行为例进行可视化查询。

5.1 查看所有关联实体

match p=(m)-[]->(n) where m.股票名称="平安银行" or n.股票名称="平安银行" return p;

5.2 限制显示数量

计算股票间对数收益率的相关系数后,查看与平安银行股票相关联的实体

match p=(m)-[]->(n) where m.股票名称="平安银行" or n.股票名称="平安银行" return p limit 300;

5.3 指定股票间对数收益率相关系数

match p=(m)-[]->(n) where m.股票名称="平安银行" and n.股票名称="万科A" return p;

6 neo4j 图算法

6.1.中心度算法(Centralities)

6.2 社区检测算法(Community detection)

6.3 路径搜索算法(Path finding)

6.4 相似性算法(Similarity)

6.5 链接预测(Link Prediction)

6.6 预处理算法(Preprocessing)

6.7 算法库安装及导入方法

以Windows OS为例,neo4j的算法库并非在安装包中提供,而需要下载算法包:

(1)下载graph-algorithms-algo-3.5.4.0.jar

(2)将graph-algorithms-algo-3.5.4.0.jar移动至neo4j数据库根目录下的plugin

(3)修改neo4j数据库目录的confneo4j.conf,添加以下配置

dbms.security.procedures.unrestricted=algo.*

(4)使用以下命令查看所有算法列表

CALL algo.list()

6.8 算法实践——链路预测

6.8.1 Aaamic Adar algorithm

主要基于判断相邻的两个节点之间的亲密程度作为评判标准,2003年由Lada Adamic 和 Eytan Adar在 Friends and neighbors on the Web 提出,其中节点亲密度的计算公式如下:

<img src="https://github.com/jm199504/Financial-Knowledge-Graphs/blob/master/images/aaa.png?raw=true" style="zoom: 33%;" />

其中N(u)表示与节点u相邻的节点集合,若A(x,y)表示节点x和节点y不相邻,而该值若越大则紧密度为高。

AAA 算法 cypher 代码参考:

MERGE (zhen:Person {name: "Zhen"})
MERGE (praveena:Person {name: "Praveena"})
MERGE (michael:Person {name: "Michael"})
MERGE (arya:Person {name: "Arya"})
MERGE (karin:Person {name: "Karin"})

MERGE (zhen)-[:FRIENDS]-(arya)
MERGE (zhen)-[:FRIENDS]-(praveena)
MERGE (praveena)-[:WORKS_WITH]-(karin)
MERGE (praveena)-[:FRIENDS]-(michael)
MERGE (michael)-[:WORKS_WITH]-(karin)
MERGE (arya)-[:FRIENDS]-(karin)

// 计算 Michael 和 Karin 之间的亲密度
MATCH (p1:Person {name: 'Michael'})
MATCH (p2:Person {name: 'Karin'})
RETURN algo.linkprediction.adamicAdar(p1, p2) AS score
// score: 0.910349

// 基于好友关系计算 Michael 和 Karin 之间的亲密度
MATCH (p1:Person {name: 'Michael'})
MATCH (p2:Person {name: 'Karin'})
RETURN algo.linkprediction.adamicAdar(p1, p2, {relationshipQuery: "FRIENDS"}) AS score
// score: 0.0                                                

6.8.2 Common Neighbors

基于节点之间共同近邻数量计算,计算公式如下:

<img src="https://github.com/jm199504/Financial-Knowledge-Graphs/blob/master/images/cn.png?raw=true" style="zoom:33%;" />

其中N(x)表示与节点x相邻的节点集合,共同近邻表示两个集合的交集,若CN(x,y)值越高,表示节点x和节点y的亲密度越高。

Common Neighbors算法 cypher 代码参考:

MATCH (p1:Person {name: 'Michael'})
MATCH (p2:Person {name: 'Karin'})
RETURN algo.linkprediction.commonNeighbors(p1, p2) AS score

6.8.3 Resource Allocation

资源分配算法,计算公式如下:

<img src="https://github.com/jm199504/Financial-Knowledge-Graphs/blob/master/images/ra.png?raw=true" style="zoom:33%;" />

其中N(u)是与节点u相邻的节点集合,RA(x,y)越高表明节点x和节点y的亲密度越大。

Resource Allocation算法 cypher 代码参考:

MATCH (p1:Person {name: 'Michael'})
MATCH (p2:Person {name: 'Karin'})
RETURN algo.linkprediction.resourceAllocation(p1, p2) AS score

6.8.4 Total Neighbors

指的是相邻节点之间的邻居总数,计算公式如下:

<img src="https://github.com/jm199504/Financial-Knowledge-Graphs/blob/master/images/tn.png?raw=true" style="zoom:33%;" />

其中N(u)是与节点u相邻的节点集合。

Total Neighbors算法 cypher 代码参考:

MATCH (p1:Person {name: 'Michael'})
MATCH (p2:Person {name: 'Karin'})
RETURN algo.linkprediction.totalNeighbors(p1, p2) AS score

官网文档=>链路算法=>介绍:https://neo4j.com/docs/graph-algorithms/3.5/labs-algorithms/linkprediction/