import datetime,os
import math
import time
import multiprocessing,threading # 导入多进程和多线程包
import numpy as np
import pandas as pd
import tushare as ts
from matplotlib import pyplot as plt
ts.set_token('c1fa5bbb1934cf23e053974c6eb58ffbf2713312c520a2e1074d5090')
pro = ts.pro_api()
'''
关于本函数库的说明:
1.本函数库使用的是类方法属性,@classmethod。关于类以及此语法的用法,留待日期再认真研究。
使用类的好处,方便使用时可以快速查找到对应的函数,可以大大减少记忆量。
2.文件保存方面:
a.文件保存的路径前缀在函数Basics.PandasSaveData、Basics.IsNewDataFile的参数path_prefix中设置
b.文件保存后缀的设置格式:'/类名/函数名/文件名'
3.暂定设置以下类:
Basics 基础类
BasicsData 基础数据类
Financial 财务类
Technology 技术分析类
Chart 图表类
Index 指标类
Fund 资金类
Price 价格类
Volume 成交量类
Industry 行业概念类
WanWei 万维股票分析系统
Upgrade 待升级的函数的类
Immaturity 未成熟的函数
'''
def ShowFunc():
# 技术类
Technology.Hist_List()
Technology.Day_Index_Hist()
Technology.MA_Data_Func()
Technology.MA_Figure()
Technology.Judge_MA()
Technology.MACD_Data_Func()
Technology.MACD_Figure()
Technology.Judge_MACD()
Technology.MACD_FourCrossingAnalysis()
Technology.MACD_FourCrossingAnalysisResult()
# TODO 将成交量分析函数化为技术类中
def Today_Date_Count(Num=0): # 计算当前日期加减一个数字,向前或向后得出新的日期
'''
:param Num: 当前日期向前或向后加减的天数:正数为向后推算,负数为向前推算
:return:
'''
now = datetime.datetime.today()
result_time=now+datetime.timedelta(days=Num)
year = result_time.year
month = result_time.month
day = result_time.day
year_str = '{}'.format(year)
month_str = '{}'.format(month).rjust(2, '0')
day_str='{}'.format(day).rjust(2,'0')
date_str='{}{}{}'.format(year_str,month_str,day_str)
return date_str
# MACD_FourCrossingAnalysis(RecentTradeDayNum=2)
# MACD_FourCrossingAnalysisResult()
# TODO 1.均线作为支撑线,验证格南维尔法则的不同买卖时点的概率。2.分析多头排列股份持续上升的天数,并统计其分步规律。
# TODO 1.优化MA多头排列的判断方法,是否可以使用相邻MA5-MA10的变化差的波动百分比来判断.2。增加参数judge_num的天数来判断
def PAN(target='pb'):
pb_df=pd.read_csv('./LoadFile/LongUpdateData/财务{}统计结果.csv'.format(target))
pb_index=[i[0:6] for i in pb_df.loc[:,'ts_code'].values]
pb_df.index=pb_index
pb_df.drop(columns=['Unnamed: 0'],inplace=True)
print(pb_df)
# 获取每日指标:使用for循环,但是使用了for break语句
for i in range(365):
trade_date=Today_Date_Count(Num=-i)
print(trade_date)
df = pro.daily_basic(ts_code='', trade_date=trade_date,
fields='ts_code,trade_date,{}'.format(target))
num=df['ts_code'].count()
print(i,end='\t')
print(num)
if num>0:
break
df.index=[i[0:6] for i in df.loc[:,'ts_code']] # TODO 此处语法有点瑕疵,有待优化
df.drop(columns=['ts_code'], inplace=True)
print(df)
# 合并数据
result=pb_df.join(df)
print(result)
y=result.columns.values[2:9]
print(y)
def judge(t):
bool_list=[]
for i in y:
if t[target]>t[i]:
bool_list.append(t[i])
len1=len(bool_list)
return y[len1-1] # TODO 此语法有瑕疵,应为 return y[len1],但运行此语句时程序报错
result['judge']=result.apply(judge,axis=1)
print(result)
result.to_csv('20230512临时数据.csv', encoding='utf_8_sig')
def ListDaysYears(): # 关于上市天数和年数的函数
'''
:return:返回一个元组。元组中包括两个变量:
data:DataFrame数据,包含A股所有上市公司的上市年数数据,list_years列中数据为上市公司的自上市至今的上市年数
running_time:函数的运行时间
'''
# 计算函数运行时间的辅助代码
begin_time=datetime.datetime.now()
# 获取A股所有股票的上市公司代码、名称、上市时间数据
data = pro.stock_basic(exchange='', list_status='L', fields='ts_code,symbol,name,list_date')
symbol_list=[i for i in data.loc[:,'symbol'].values]
data.index=symbol_list
# 使用循环获向上市天数和年数列表中添加数据
list_days=[]
list_years=[]
for i in symbol_list:
old_date_str = data.loc[i, 'list_date'] # 由上市日期构成的字符串
new_date_str = old_date_str[0:4] + '-' + old_date_str[4:6] + '-' + old_date_str[6:] # 将date_str1转化为YYYY-MM-DD格式
List_Date = datetime.date.fromisoformat(new_date_str) # 上市日期
today = datetime.date.today() # 今天的日期
ListDaysNum = (today - List_Date).days # 上市时间天数,该天数为自然日天数,不是交易日天数
ListYearNums = round(ListDaysNum / 365, 2) # 上市时间年数
list_days.append(ListDaysNum)
list_years.append(ListYearNums)
else:
data['list_days']=list_days # 向原data数据中添加上市日期天数,即list_days
data['list_years']=list_years # 向原data数据中添加上市年数列,即list_years
data.index=range(data['ts_code'].count()) # 按股票代码出现的顺序号设置行索引
# 计算函数运行时间的辅助代码
end_time=datetime.datetime.now()
running_time=end_time-begin_time
myresult=(data,running_time)
return myresult
class Basics(): # 基础类
@classmethod
def RecentlyTradeDate(cls):
# 获取A股股票数据
data = pro.stock_basic(exchange='', list_status='L', fields='ts_code,symbol,name,list_date')
# 获取股标ts_code构成的列表中第一个元素
ts_code = data['ts_code'][0]
# 获取变量ts_code的日线行情
df = pro.daily(ts_code=ts_code, start_date='20180701')
# 获取变量ts_code的最近一个交易日
recently_trade_date = df['trade_date'][0]
# 函数返回值
return recently_trade_date
@classmethod
def PandasSaveData(cls,data, path_suffix, save_file_type='csv', path_prefix='D:\StockAnalysis',encoding='utf_8_sig', index=False, header=True):
'''
:param data: 需要导入的pandas数据
:param path_suffix:保存文件路径的后缀。数据格式:'/张三/李四/王五/赵六/测试函数{}-{}.xlsx'.format(k1,k2)。
使用技巧:可以在使用SaveData函数前,定义一个路径变量,然后再使用SaveData函数。这个路径变量中可以包含生成该数据的函数中的参数
data=pd.DataFrame([[4, 9, 16], [25, 36, 49], [64, 81, 100]], columns=list('ABC'))
k1='财务'
k2='刘洋'
my_path_suffix= '/张三/李四/王五/赵六/测试函数{}-{}.xlsx'.format(k1, k2)
SaveData(data=data, path_suffix=my_path_suffix, save_file_type='excel')
:param save_file_type:导出文件的格式,分为csv和excel两种,默认值为csv。参数值只有两个:csv和excel
:param path_prefix:保存文件路径的前缀,即将文件统一保存至某个文件夹中。数据格式:'E:\小白课堂\证券投资\立讯精密'。路径名称的最后一个文件夹名称后面不许带有符号'\'
:param encoding: 编码格式,设置的默认值为utf_8_sig,可以正常显示汉字
:param index: 是否需要导入行索引,默认为Flase,即不导入行索引
:param header: 是否需要导入列索引,默认为True,即导入行索引
:return:1.将数据导出成csv或excel格式文件
2.返回文件的保存路径
:自我评价:1.从学习pthon以来,这是创建的参数最多的一个函数,这也是一个很漂亮的函数
2.该函数运行时间极短,大约为0.001秒
'''
start_time = datetime.datetime.now()
# 1.将参数中的路径前缀转化为python格式的路径,并前缀电脑中不存在前缀路径,则程序创建一个前缀路径
prefix = path_prefix.replace('\\', '/')
prefix_list = prefix.split(sep='/')
mypath_prefix = '' # 使用循环创建前缀路径
for i in prefix_list:
if i == prefix_list[0]:
mypath_prefix = mypath_prefix + i
if os.path.exists(mypath_prefix):
pass
else:
os.mkdir(mypath_prefix)
else:
mypath_prefix = mypath_prefix + '/' + i
if os.path.exists(mypath_prefix):
pass
else:
os.mkdir(mypath_prefix)
# 2.创建路径后缀中的文件夹
suffix_list1 = path_suffix.split(sep='/') # 将路径后缀按'/'进行分析,并将分割结果返回成列表数据
lenght = len(suffix_list1)
suffix_list2 = suffix_list1[1:lenght - 1] # 列表2中的元素,从前至后,依次为各级文件的名称
mypath = prefix # 为使用循环反复赋值定义的一个路径变量
for i in suffix_list2:
mypath = mypath + '/' + i
if os.path.exists(mypath): # 判断文件夹或文件是否存在的函数
pass
else:
os.mkdir(mypath) # 创建文件夹
# 3.删除旧文件并创建新文件
del_name = suffix_list1[-1] # 旧文件名称中的相同文件
del_name_list = del_name.split(sep='.') # split将del分割为'.'前后两部分
identifier=Basics.RecentlyTradeDate()
'''
20230623年新增功能,以交易日期作为文件是否更新标识符更合理
'''
file_name=del_name_list[0]+'(交易日期{})'.format(identifier)+'.'+del_name_list[1]
final_save_path = mypath + '/' + file_name # 创建文件的最终路径和名称
if os.path.exists(final_save_path): # 通过标识符变量判断,是否为最新文件。如果是最新文件,则不需要重复创建,否则,删除旧文件并创建新文件
print('文件已存在,不需要重复创建')
else:
# 删除旧文件
use_file_list = os.listdir(mypath)
for j in use_file_list:
if j.count(del_name_list[0]) != 0 and j.count(del_name_list[1]) !=0:
remove_file_name = mypath + '/' + j
os.remove(remove_file_name)
# 创建新文件
if save_file_type == 'csv':
data.to_csv(final_save_path, encoding=encoding, index=index, header=header)
print('文件创建完成')
elif save_file_type == 'excel':
data.to_excel(final_save_path, encoding=encoding, index=index, header=header)
print('文件创建完成')
end_time = datetime.datetime.now()
print(end_time - start_time)
# 4.函数返回值
return final_save_path
@classmethod
def IsNewDataFile(cls,path_suffix, path_prefix='D:\StockAnalysis'):
'''
:param path_suffix: 文件的只在路径,不需要带本是否更新标识符,如file_path = '/Financial/FinancialItem/股票[{}-{}]所有季度数据.csv'.format(statement_type, item)
:param path_prefix: 参考Basics.PandasSaveData()说明
:return: 返回值是一个元组:1.返回True或False,如果是最新的文件,则返回True,否则返回False
2.返回文件的保存路径,路径返回值是同Basics.PandasSaveData()一致。
'''
# 1.将参数中的路径前缀转化为python格式的路径,并前缀电脑中不存在前缀路径,则程序创建一个前缀路径
prefix = path_prefix.replace('\\', '/')
prefix_list = prefix.split(sep='/')
mypath_prefix = '' # 使用循环创建前缀路径
for i in prefix_list:
if i == prefix_list[0]:
mypath_prefix = mypath_prefix + i
if os.path.exists(mypath_prefix):
pass
else:
os.mkdir(mypath_prefix)
else:
mypath_prefix = mypath_prefix + '/' + i
if os.path.exists(mypath_prefix):
pass
else:
os.mkdir(mypath_prefix)
# 2.创建路径后缀中的文件夹
suffix_list1 = path_suffix.split(sep='/') # 将路径后缀按'/'进行分析,并将分割结果返回成列表数据
lenght = len(suffix_list1)
suffix_list2 = suffix_list1[1:lenght - 1] # 列表2中的元素,从前至后,依次为各级文件的名称
mypath = prefix # 为使用循环反复赋值定义的一个路径变量
for i in suffix_list2:
mypath = mypath + '/' + i
if os.path.exists(mypath): # 判断文件夹或文件是否存在的函数
pass
else:
os.mkdir(mypath) # 创建文件夹
# 3.删除旧文件并创建新文件
del_name = suffix_list1[-1] # 旧文件名称中的相同文件
del_name_list = del_name.split(sep='.') # split将del分割为'.'前后两部分
identifier=Basics.RecentlyTradeDate()
'''
20230623年新增功能,以交易日期作为文件是否更新标识符更合理
'''
file_name = del_name_list[0] + '(交易日期{})'.format(identifier) + '.' + del_name_list[1]
final_save_path = mypath + '/' + file_name # 创建文件的最终路径和名称
if os.path.exists(final_save_path): # 通过标识符变量判断,是否为最新文件。如果是最新文件,则不需要重复创建,否则,删除旧文件并创建新文件
return (True,final_save_path)
else:
return (False,final_save_path)
@classmethod
def PandasMultipleBool(cls,data, col, mylist: list): # 可以参考此函数写一个关于'&'符的多条件布尔索引
'''
:param data: 需要使用多个"或条件"索引的pandas数据
:param col: 需要使用多个"或条件"索引的pandas殉索引
:param mylist:需要使用多个"或条件"索引的多个条件
:return: 返回多个"或条件"索引的pandas数据结果
'''
# 1.使用concat函数将多个"或"即"|"条件索引出来的数据拼接在一起,即实现了多个"或条件"即"|"条件下的pandas布尔索引
result = pd.DataFrame()
for i in mylist:
bool = data[data[col] == i]
result = pd.concat([result, bool])
# 2.将获得的数据按'ts_code'列的值进行排列,并重新设置行索引
mydf = result.sort_values(by=['ts_code'], ascending=True, inplace=False)
num = result['ts_code'].count()
mydf.index = [i for i in range(num)]
# 3.函数返回值
return mydf
@classmethod
def WholeFilePath(cls,path_suffix, path_prefix='D:\StockAnalysis'):
'''
: param path_suffix: 手工添加文件的路径前辍,格式:path_suffix='/ManualEntryFile(NoDelete)/HighQualityStock/优质股.xlsx'
: param path_prefix: 参考Basics.PandasSaveData()说明
: return: 返回一个完整的文件路径
'''
# 1.将参数中的路径前缀转化为python格式的路径,并前缀电脑中不存在前缀路径,则程序创建一个前缀路径
prefix = path_prefix.replace('\\', '/')
prefix_list = prefix.split(sep='/')
mypath_prefix = '' # 使用循环创建前缀路径
for i in prefix_list:
if i == prefix_list[0]:
mypath_prefix = mypath_prefix + i
if os.path.exists(mypath_prefix):
pass
else:
os.mkdir(mypath_prefix)
else:
mypath_prefix = mypath_prefix + '/' + i
if os.path.exists(mypath_prefix):
pass
else:
os.mkdir(mypath_prefix)
# 2.生成一个完成的文件路径
whole_path = mypath_prefix + path_suffix
# 3.函数返回值
return whole_path
@classmethod
def ReadManualFileData(cls,path_suffix):
'''
:param path_suffix: 手写文件的路径后辍,含义参见Basics.PandasSaveData()中相同参数的解释
:return: 返回一个由手写的文件数据生成的dataframe
:应用价值:
TODO
1.可以将BasicData.Save_Manual_SW()和BasicData.Read_Manual_SW()两个函数优化掉,因
为这两个函数中涉及到绝对路径问题,如果不优化掉,程序放在别的电脑上运行,会出现错误。
2.BasicData.Save_Manual_SW()和BasicData.Read_Manual_SW()两个函数中的功能,可以由
本函数更简洁的实现。
'''
# 1.获取完整路径
path = Basics.WholeFilePath(path_suffix=path_suffix)
# 2.读取文件数据
if path_suffix[-4:] == 'xlsx':
df = pd.read_excel(path)
if 'Unnamed: 0' in df.columns.values:
df.drop(labels='Unnamed: 0', axis=1, inplace=True)
return df
elif path_suffix[-3:] == 'csv':
df = pd.read_csv(path)
if 'Unnamed: 0' in df.columns.values:
df.drop(labels='Unnamed: 0', axis=1, inplace=True)
return df
else:
return '可能数据格式或路径后辍错误'
@classmethod
def MyTimer(cls,year, month, day, hour, minute, second,function,arguments_dict=None):
'''
编写本函数的参考资料:
https://blog.csdn.net/u010701274/article/details/122958603
https://www.jb51.net/article/277143.htm
:param function: 函数名称,不带名称后面的括号
:param arguments_dict: 用字典接收函数的参数,类似多进程kwargs参数
:param year: 年数
:param month: 月份
:param day: 日期
:param hour:小时
:param minute:分钟
:param second:妙
:return: 定时启动某个函数
'''
# 当前时间
now = datetime.datetime.now()
# 开始时间
start_time = datetime.datetime(year=year, month=month, day=day, hour=hour, minute=minute, second=second,
microsecond=0)
# 时间差
time_interval = (start_time - now).total_seconds()
# 多线程定时器
pro = threading.Timer(interval=time_interval, function=function, kwargs=arguments_dict)
pro.start()
class Financial(): # 财务类
@classmethod
def PeriodFinancial(cls,statement_type, period, item): # 一次获取A股某个报告期的某种财务报表的函数
# TODO 1.删除不需要的以A开头的数据;2.股票代码的排序问题
'''
该函数运行时间大概5~13秒
:param statement_type: 财务报表类型,
income 表示利润表
balancesheet 表示资产负债表
cashflow 表示现金流量表
:param period:财务报表的季度报告期,比如,20181231,是2018年第4季度的报告期
:param item:财务报表中的项目。切记,item参数必须对应财务报表类型中的项目,否则可能报错。
item的参数名称见以下三个接口的输出参数
利润表income:https://www.tushare.pro/document/2?doc_id=33
资产负债表balancesheet:https://www.tushare.pro/document/2?doc_id=36
现金流量表cashflow:https://www.tushare.pro/document/2?doc_id=44
:return:返回数据类型是一个元组,包含Drop_Df2:某季度A股某个财务报表中对应项目中所有股票对应在数据,running_time:函数运行时间
:应用价值:
1.制作A股市场成立以来,股票某个财务报表项目对应项目自上市以来的所有报告期的数据,该数据包含A股所有股票。
2.统计申万行业某季度财务报表的项目的均值,最大值,最小值等统计数据。
3.计算申万行业某季度财务报表的项目的排名。
'''
begin_time = datetime.datetime.now() # 测试函数运行时间的辅助代码
# 1.设置一个条件判断,当type为不同值时,调用A股某季度不同的财务报表的相应报表项目的所有数据
global period_financial_df
if statement_type == 'income':
period_financial_df = pro.income_vip(period=period,
fields='ts_code,ann_date,f_ann_date,end_date,{}'.format(item))
elif statement_type == 'balancesheet':
period_financial_df = pro.balancesheet_vip(period=period,
fields='ts_code,ann_date,f_ann_date,end_date,{}'.format(item))
elif statement_type == 'cashflow':
period_financial_df = pro.cashflow_vip(period=period,
fields='ts_code,ann_date,f_ann_date,end_date,{}'.format(item))
num = period_financial_df['ts_code'].count() # 获取的原始数据中的股票个数(该数据未去重)
# 2.添加辅助列times,该列数值表示该ts_code出现的次数,即判断是否有重复的股票数据
ts_code_list = [i for i in period_financial_df['ts_code'].values] # 所有ts_code码构成的列表
times_list = [] # 股票出现次数列表,用于添加辅助列,在导出的csv文件中方便查看哪里股票代码是重复的
for i in range(num): # 构造一个循环,向times_list中添加数据
times_num = ts_code_list.count(ts_code_list[i]) # 股票Ts码出现的次数
times_list.append(times_num)
period_financial_df['times'] = times_list # 在period_financial_df中增加times列
# 3.测试另一种方法添加次数列,以及判断日期是否为最新标记列。事实证明两种判断是否重复的方法等效,因为times和times1两列数据完全一样
time_list2 = [] # 另一种判断股票出次数的辅助列,含义同times_list
is_new = [] # 判断实际公告日期f_ann_date中数据是否为最新日期的列表
for i in range(num): # 构造一个循环,生成time_list2和is_new两个中的数据
Ts_Code = ts_code_list[i] # ts_code_list中第i个股票TS码
Bool_Df = period_financial_df[
period_financial_df['ts_code'] == Ts_Code] # 通过布尔索引,获取period_financial_df列索引为ts_code的列中数值等于i的所有数据
count_num = Bool_Df['ts_code'].count() # 切片后df获得数据的行数,等于某个股票出现的重复数
time_list2.append(count_num) # 向time_list2中添加数据
f_list = [eval(i) for i in Bool_Df['f_ann_date'].values] # 切片后df中f_ann_date列中的数据,并将其转化为int类型添加至f_list
ts_f_date = eval(period_financial_df.loc[i, 'f_ann_date']) # 某行股票ts_code码对应f_ann_date列交叉点位置的实际报告日期
# 构造一个双重判断,判断实际公告日期是否为最新日期
if count_num == 1: # 如果不重复,判断为最新日期
is_new.append('Y')
else:
if ts_f_date == max(f_list): # 如果重复但f1是f_list中最大值,判断为最新日期
is_new.append('Y')
else: # # 如果重复但f1不是f_list中最大值,判断为非最新日期
is_new.append('N')
period_financial_df['times1'] = time_list2 # period_financial_df中添加times1辅助列
period_financial_df['is_new'] = is_new # period_financial_df中添加is_new辅助列
bool_df = period_financial_df[
period_financial_df['is_new'] == 'Y'] # 通过布尔索引筛选period_financial_df为最新日期为数据,并赋值为了一个变量
# 去掉bool_df中ts_code列中重复数据
Drop_Df1 = bool_df.drop_duplicates(subset='ts_code', keep='last',
inplace=False) # 对ts_code去重,数据保最后出现的数据,并赋值给一个变量
# 删除Drop_Df1不需要的列
Drop_Df2 = Drop_Df1.drop(columns=['ann_date', 'f_ann_date', 'end_date', 'times', 'times1', 'is_new'],
inplace=False) # 删除不需要的列,并赋值给一个变量
Drop_Df2.columns = ['ts_code', period] # 重新设置列索引
Drop_Df2.index = [i for i in Drop_Df2['ts_code'].values] # 重新设置行索引
# 测试函数运行时间的辅助代码
end_time = datetime.datetime.now()
running_time = end_time - begin_time
# 4.函数返回值
myresult = (Drop_Df2, running_time)
return myresult
@classmethod
def FinancialItem(cls,statement_type, item): # 一次性获取A股所有股票,自上市时间至今的某个财务报表项目全部数据
'''
该函数运行时间大概6分钟左右
:param statement_type: 财务报表类型,
income 表示利润表
balancesheet 表示资产负债表
cashflow 表示现金流量表
:param item:财务报表中的项目。切记,item参数必须对应财务报表类型中的项目,否则可能报错。
item的参数名称见以下三个接口的输出参数
利润表income:https://www.tushare.pro/document/2?doc_id=33
资产负债表balancesheet:https://www.tushare.pro/document/2?doc_id=36
现金流量表cashflow:https://www.tushare.pro/document/2?doc_id=44
:return:1.返回值:函数运行时间
2.生成文件:获取A股自上市以来所有季度某个财务数据的单个数据,并将这个数据导出成csv文件
:应用价值:
1.统计申万行业某季度财务报表的项目的均值,最大值,最小值等统计数据。
2.计算申万行业某季度财务报表的项目的排名。
3.制作股票某个指标不同季度不同年份变化的可视化图表
'''
start_time = datetime.datetime.now() # 为计算函数运行时间而书写的辅助代码
file_path = '/Financial/FinancialItem/股票[{}-{}]所有季度数据.csv'.format(statement_type, item)
bool_value,final_save_path_str=Basics.IsNewDataFile(path_suffix=file_path)
if bool_value: # 增加一个逻辑判断,如果是最新文件,则不需要重新生成文件
print('已是最新文件,无需更新!')
else:
# 1.获取当年年份
MYTODAY = datetime.date.today()
year = MYTODAY.year
# 2.创建报告日期列表
report_date_list = []
for i in range(1990, year):
str1 = str(i) + '0331'
str2 = str(i) + '0630'
str3 = str(i) + '0930'
str4 = str(i) + '1231'
report_date_list.append(str1)
report_date_list.append(str2)
report_date_list.append(str3)
report_date_list.append(str4)
today_str = datetime.date.today().__format__('%Y%m%d')
if eval(today_str) > eval(str(year) + '0331'):
report_date_list.append(str(year) + '0331')
if eval(today_str) > eval(str(year) + '0630'):
report_date_list.append(str(year) + '0630')
if eval(today_str) > eval(str(year) + '0930'):
report_date_list.append(str(year) + '0930')
if eval(today_str) > eval(str(year) + '1231'):
report_date_list.append(str(year) + '1231')
report_date_list.sort(reverse=True) # 对列表数据进行排序,最近的日期排在最前面
# print(report_date_list[:-3])
# 3.获取当前上市的股票列表
data = pro.query('stock_basic', exchange='', list_status='L', fields='ts_code,symbol,name')
data.index = [i for i in data['ts_code'].values] # 为使用join函数拼接数据,此处需要修改data的行索引。
# 4.使用循环获取A股自上市以来所有报告的某个财务数据
for i in report_date_list[:-3]: # 使用循环方式获取所有报告期中财务数据,report_date_list[:-3]表示取report_date_list中自列表开头至倒数第4之间所有数据
my_df, _ = Financial.PeriodFinancial(statement_type=statement_type, period=i,
item=item) # 调用PeriodFinancial获取某个季度的所有财务数据
index_i = report_date_list.index(i) # i所在列表中的位置
print(f'获取财务报表{statement_type}_{item}:序号{index_i + 1}\t季度{i}')
my_df.drop(columns=['ts_code'], inplace=True) # 删除多余的列
data = data.join(my_df) # 使用join将获取的季度数据拼接同data进行接接,并将结果再次赋值给data变量
else:
# file_path = '/Financial/FinancialItem/股票[{}-{}]所有季度数据.csv'.format(statement_type, item)
# final_save_path_str=Basics.PandasSaveData(data=data,path_suffix=file_path)
Basics.PandasSaveData(data=data, path_suffix=file_path)
# # 为计算函数运行时间而书写的辅助代码
end_time = datetime.datetime.now()
running_time = end_time - start_time
# 函数返回值
return (final_save_path_str,running_time)
@classmethod
def PeriodIndicator(cls,period, indicator): # 获取某一期所有股票财务指标的函数
'''
:param period: 财务报表的季度报告期,比如,20181231,是2018年第4季度的报告期
:param indicator: 参数及其含义如下:
profit_dedt 扣除非经常性损益后的净利润(扣非净利润) 对应同花顺中扣非净利润
netprofit_margin 销售净利率 对应同花顺相应名称
grossprofit_margin 销售毛利率 对应同花顺相应名称
roe_waa 加权平均净资产收益率 好像对应同花顺的净资产收益率
其他参数值解释见:https://www.tushare.pro/document/2?doc_id=79
:return: 返回数据类型是一个元组,包含myresult:某季度A股所有股票财务指标数据接口中某个指标所有数据,running_time:函数运行时间
:应用价值:
1.制作A股市场成立以来,某个财务指标自上市以来的所有报告期的数据,该数据包含A股所有股票。
2.统计申万行业某季度财务指标均值,最大值,最小值等统计数据。
3.计算申万行业某季度财务指标的排名。
4.关于该函数的应用价值理解参考函数PeriodFinancial
'''
start_time = datetime.datetime.now() # 计算函数运行时间的辅助代码
# 1.参数income的vip接口写法,书写获取单季度财务指标的写法。此写法网页没有直接给出来
data = pro.fina_indicator_vip(period=period,
fields='ts_code,ann_date,end_date,{},update_flag'.format(indicator))
data['is_A'] = [True if i[0] == "A" else False for i in data['ts_code'].values] # 使用三目运算符添加判断
# 向data中添加times_num列,用于判断数据重复次数
num = data['ts_code'].count()
ts_code_list = [i for i in data['ts_code'].values] # 所有ts_code码构成的列表
times_list = [] # 股票出现次数列表,用于添加辅助列,在导出的csv文件中方便查看哪里股票代码是重复的
for i in range(num):
times_num = ts_code_list.count(ts_code_list[i])
times_list.append(times_num)
data['times_num'] = times_list
# 3.增加判断是否保留数据的列,其值如果为Y,则切片时会保留,否则不保留
if num != 0: # 如果获取到的该季度数据不为空,则执行使用apply增加is_need列,否则直接赋值为空列
def is_need_func(t): # 判断数据是否保留的函数
# global is_need_str
if t['is_A'] == False:
if t['times_num'] == 1:
is_need_str = 'Y'
else: # TODO 经验证,自上次所创建以来至2023年5月19日,所股票且所有报告期的重复次数最大为2.但是如果最大重复数超过3,则判断逻辑需要重新写。
if t['update_flag'] == '1':
is_need_str = 'Y'
else:
is_need_str = 'N'
else:
is_need_str = 'N'
return is_need_str
data['is_need'] = data.apply(is_need_func, axis=1)
else:
data['is_need'] = []
# 4.通过布尔索引对is_need数值等于Y的数据进行切片
data_bool = data[data['is_need'] == 'Y']
# 5.将data_bool中不需要的列删除,并重新设置行索引和列索引
myresult = data_bool.drop(columns=['ann_date', 'end_date', 'update_flag', 'is_A', 'times_num', 'is_need'],
inplace=False) # 删除不需要的列
myresult.index = range(myresult['ts_code'].count()) # 重新设置行索引
myresult.columns = ['ts_code', period] # 重新设置列索引
# 6.计算函数运行时间的辅助代码
end_time = datetime.datetime.now()
running_time = end_time - start_time
# 7.函数返回值
return (myresult, running_time)
@classmethod
def FinaIndicator(csl,item):
'''
TODO 1.该函数可以指定季度列表,比如,不指定则默认获取所有季度,若指定季度,则可以按指定季度获取数据,这样可以方便统计
TODO 2.可以设置一个参数,用来指定获取数据,是否以亿元为单位,并且结果保留2位小数。该功能同样适用于FinancialAll函数
TODO 3.A股上市股票名称,在其他函数中使用时,需要对代码值进行排序。
评价:增加参数可以使函数处理的问题更加具有通用性,更有普遍性。
:param item:财务指标数据接口中的的某个输出参数,参数含义如下:
profit_dedt 扣除非经常性损益后的净利润(扣非净利润) 对应同花顺中扣非净利润
netprofit_margin 销售净利率 对应同花顺相应名称
grossprofit_margin 销售毛利率 对应同花顺相应名称
roe_waa 加权平均净资产收益率 好像对应同花顺的净资产收益率
其他参数值解释见:https://www.tushare.pro/document/2?doc_id=79
:return:1.返回值:函数运行时间
2.生成文件:获取A股自上市以来所有季度某个财务指标数据,并将这个数据导出成csv文件
:应用价值:
1.统计申万行业某季度财务指标的均值,最大值,最小值等统计数据。
2.计算申万行业某季财务指标的排名。
3.制作股票某个财务指标不同季度不同年份变化的可视化图表
4.该函数的应用价值解释,可以参考函数QuantileDaily理解
'''
start_time = datetime.datetime.now()
path_my_str16891 = '/Financial/FinaIndicator/财务指标{}.csv'.format(item)
bool_value,final_save_path_str=Basics.IsNewDataFile(path_suffix=path_my_str16891)
if bool_value: # 增加一个逻辑判断,如果是最新文件,则不需要重新生成文件
print('已是最新文件,无需更新!')
else:
# 1.获取当年年份
MYTODAY = datetime.date.today()
year = MYTODAY.year
# 2.创建报告日期列表
report_date_list = []
for i in range(1990, year):
str1 = str(i) + '0331'
str2 = str(i) + '0630'
str3 = str(i) + '0930'
str4 = str(i) + '1231'
report_date_list.append(str1)
report_date_list.append(str2)
report_date_list.append(str3)
report_date_list.append(str4)
today_str = datetime.date.today().__format__('%Y%m%d')
if eval(today_str) > eval(str(year) + '0331'):
report_date_list.append(str(year) + '0331')
if eval(today_str) > eval(str(year) + '0630'):
report_date_list.append(str(year) + '0630')
if eval(today_str) > eval(str(year) + '0930'):
report_date_list.append(str(year) + '0930')
if eval(today_str) > eval(str(year) + '1231'):
report_date_list.append(str(year) + '1231')
report_date_list.sort(reverse=True) # 对列表数据进行排序,最近的日期排在最前面
# 3.获取当前上市的股票列表
data = pro.query('stock_basic', exchange='', list_status='L', fields='ts_code,symbol,name')
data.sort_values(by=['symbol'], ascending=True, inplace=True)
data.index = [i for i in data['ts_code'].values] # 为使用join函数拼接数据,此处需要修改data的行索引。
# 4.使用循环获取A股自上市以来所有报告的某个财务数据
for i in report_date_list[:-3]: # 使用循环方式获取所有报告期中财务数据,report_date_list[:-3]表示取report_date_list中自列表开头至倒数第4之间所有数据
my_df, i_time = Financial.PeriodIndicator(period=i, indicator=item) # 调用PeriodFinancial获取某个季度的所有财务数据
my_df.index = [i for i in my_df['ts_code'].values]
my_df.drop(columns=['ts_code'], inplace=True) # 删除多余的列
data = data.join(my_df) # 使用join将获取的季度数据拼接同data进行接接,并将结果再次赋值给data变量
print(i, '\t', i_time)
else:
# path_my_str16891='/Financial/FinaIndicator/财务指标{}.csv'.format(item)
# final_save_path_str=Basics.PandasSaveData(data=data,path_suffix=path_my_str16891)
Basics.PandasSaveData(data=data, path_suffix=path_my_str16891)
# # 为计算函数运行时间而书写的辅助代码
end_time = datetime.datetime.now()
running_time = end_time - start_time
# 函数返回值
return (final_save_path_str,running_time)
@classmethod
def FinancialItemAnalysis(cls,statement_type, item, report_type,is_year_report=False): # 这是一个优秀的函数
'''
:param statement_type: 财务报表或财务指标的类型,参数值的含义:
income 利润表
balancesheet 资产负债表
cashflow 现金流量表
fina_indicator 财务指标
:param item: 利润表、资产负债表、现金流量表或财务指标数据接口中输出参数中对应的参数名称
:param report_type: 参数值分别为一季报、中报,三季报或年报
:param is_year_report: 是否为年报,默认参数为False,内置函数recent_report_data中的一个参数,该内只函数的作用是添加“YOY”列,即同比列
:return: 1.生成一份csv格式分析数据。2.返回一个DataFrame,内容是财务项目的分析数据
'''
global period_date9988728 # 定义财务报表报告期的一个变量
if report_type == '一季报':
period_date9988728 = '0331'
elif report_type == '中报':
period_date9988728 = '0630'
elif report_type == '三季报':
period_date9988728 = '0930'
elif report_type == '年报':
period_date9988728 = '1231'
start_time = datetime.datetime.now()
# 1.读取数据
MYTODAY = datetime.date.today()
if statement_type == 'fina_indicator':
# file_path789125698 = './LoadFile/FinancialTargetStatistics/FinaIndicator/{}财务指标{}.csv'.format(MYTODAY,
# item)
file_path789125698,_=Financial.FinaIndicator(item=item)
else:
# file_path789125698 = './LoadFile/FinancialTargetStatistics/AllFinancialData/{}股票[{}-{}]所有季度数据.csv'.format(
# MYTODAY, statement_type, item)
file_path789125698,_=Financial.FinancialItem(statement_type=statement_type,item=item)
# file_path='./LoadFile/FinancialTargetStatistics/AllFinancialData/{}股票[{}-{}]所有季度数据.csv'.format(MYTODAY,statement_type,item)
if os.path.exists(file_path789125698): # 如果文件存在则直接读取,否则执行创建该文件的函数
print('文件已存在,可以读取')
else:
print('数据文件不存在,正在创建......')
if statement_type == 'fina_indicator':
Financial.FinaIndicator(item=item)
else:
# FinancialAll(statement_type=statement_type, item=item) # TODO 整个财务类函数前的代码
Financial.FinancialItem(statement_type=statement_type, item=item)
FinancialData = pd.read_csv(file_path789125698)
FinancialData.index = [i for i in FinancialData['ts_code'].values]
# 2023年6月11日升级代码
NoDropFinancialData = pd.read_csv(file_path789125698)
NoDropFinancialData.index = [i for i in NoDropFinancialData['ts_code'].values]
# print(FinancialData)
# 2.获取当年年份
year = datetime.date.today().year
# 3.创建报告日期列表
report_date_list = []
for i in range(1990, year):
str1 = str(i) + '0331'
str2 = str(i) + '0630'
str3 = str(i) + '0930'
str4 = str(i) + '1231'
report_date_list.append(str1)
report_date_list.append(str2)
report_date_list.append(str3)
report_date_list.append(str4)
today_str = datetime.date.today().__format__('%Y%m%d')
if eval(today_str) > eval(str(year) + '0331'):
report_date_list.append(str(year) + '0331')
if eval(today_str) > eval(str(year) + '0630'):
report_date_list.append(str(year) + '0630')
if eval(today_str) > eval(str(year) + '0930'):
report_date_list.append(str(year) + '0930')
if eval(today_str) > eval(str(year) + '1231'):
report_date_list.append(str(year) + '1231')
report_date_list.sort(reverse=True) # 对列表数据进行排序,最近的日期排在最前面
ues_list = report_date_list[:-3] # 切片去掉最后三个季度,原因是这三个季度没有数据
# print(ues_list)
# 4.获取最近三个报告期的标识符列表
FistQuarter = []
for i in ues_list:
if i[4:8] == period_date9988728:
FistQuarter.append(i)
# print(FistQuarter)
RecentYhreeQuarter = FistQuarter[0:3]
RecentYhreeQuarter.sort(reverse=False)
# print(RecentYhreeQuarter)
# 5.从原数据中删除多余的列
drop_columns = [i for i in FinancialData.columns.values] # 不需要删除的列索引
# print(drop_columns)
for i in RecentYhreeQuarter:
drop_columns.remove(i)
# print(drop_columns)
FinancialData.drop(columns=drop_columns, inplace=True)
# 6.添加各种分析列
def ThreeChange(t): # 三年连续变化值列
r1 = RecentYhreeQuarter[2] # 最近第一个报告期的列索引
r2 = RecentYhreeQuarter[1] # 最近第二个报告期的列索引
r3 = RecentYhreeQuarter[0] # 最近第三个报告期的列索引
if (t[r1] > 0 and t[r2] > 0 and t[r3] > 0) and (t[r3] < t[r2] and t[r2] < t[r1]):
add1 = round((t[r1] - t[r2]) / t[r2], 3)
add2 = round((t[r2] - t[r3]) / t[r3], 3)
add = min(add1, add2)
return '数值为正,但增长率为{}'.format(add)
elif (t[r1] < 0 and t[r2] < 0 and t[r3] < 0) and (t[r3] < t[r2] and t[r2] < t[r1]):
add1 = round((t[r1] - t[r2]) / t[r2], 3)
add2 = round((t[r2] - t[r3]) / t[r3], 3)
add = min(add1, add2)
return '数值为负,但增长率为{}'.format(add)
elif (t[r1] > 0 and t[r2] > 0 and t[r3] > 0) and (t[r3] > t[r2] and t[r2] > t[r1]):
add1 = round((t[r1] - t[r2]) / t[r2], 3)
add2 = round((t[r2] - t[r3]) / t[r3], 3)
add = max(add1, add2)
return '数值为正,但增长率为{}'.format(add)
elif (t[r1] < 0 and t[r2] < 0 and t[r3] < 0) and (t[r3] > t[r2] and t[r2] > t[r1]):
add1 = round((t[r1] - t[r2]) / t[r2], 3)
add2 = round((t[r2] - t[r3]) / t[r3], 3)
add = max(add1, add2)
return '数值为负,但增长率为{}'.format(add)
else:
return 'NO'
FinancialData['ThreeChange'] = FinancialData.apply(ThreeChange, axis=1)
# 添加同比列:2023年6月11日升级部分的内容
def recent_report_data(is_year_report=False): # 内部函数
'''
:param is_year_report: 是否为年报,默认参数为False
:return: 返回一个元组,变量的含义如下:
this_quarter816029 最近的报告日期
last_quarter816029 上一年的相同日期的报告日期
'''
year = datetime.date.today().year
today_str = datetime.date.today().__format__('%Y%m%d')
q1 = '0430'
q2 = '0731'
q3 = '1031'
q4 = '0430'
# TODO 是否将q1,q2,q3,q4设置为每个季度最后一在,在实际应用中根据需要调整
'''
上市公司的规定:
季报包括半年报披露时间是季度结束后一个月内,也就是4月,7月,10月。即4月30前、7月31日、10月31日前。
年报披露是会计年度结束后4个月内,也就是每年4月底之前,即4月30日前。
'''
global this_quarter816029, last_quarter816029
if is_year_report == True:
if eval(today_str) > eval(str(year) + q4) and eval(today_str) <= eval(str(year + 1) + q4):
this_quarter816029 = str(year) + '1231'
last_quarter816029 = str(year - 1) + '1231'
elif eval(today_str) > eval(str(year - 1) + q4) and eval(today_str) <= eval(str(year) + q4):
this_quarter816029 = str(year - 1) + '1231'
last_quarter816029 = str(year - 2) + '1231'
else:
if eval(today_str) > eval(str(year) + q1) and eval(today_str) <= eval(str(year) + q2):
this_quarter816029 = str(year) + '0331'
last_quarter816029 = str(year - 1) + '0331'
elif eval(today_str) > eval(str(year) + q2) and eval(today_str) <= eval(str(year) + q3):
this_quarter816029 = str(year) + '0630'
last_quarter816029 = str(year - 1) + '0630'
elif eval(today_str) > eval(str(year) + q3) and eval(today_str) <= eval(str(year + 1) + q4):
this_quarter816029 = str(year) + '0930'
last_quarter816029 = str(year - 1) + '0930'
elif eval(today_str) >= eval(str(year) + '0101') and eval(today_str) <= eval(str(year) + q1):
this_quarter816029 = str(year - 1) + '1231'
last_quarter816029 = str(year - 2) + '1231'
# 函数返回值
return (this_quarter816029, last_quarter816029)
this_quarter816029,last_quarter816029=recent_report_data(is_year_report=is_year_report)
ComparedWithTheSamePeriodList=[]
this_quarter816029_item_list=[]
last_quarter816029_item_list=[]
# print('添加同比列'.center(100,'-'))
# print(NoDropFinancialData)
for i in NoDropFinancialData['ts_code'].values:
v1=NoDropFinancialData.loc[i,this_quarter816029]
v2=NoDropFinancialData.loc[i,last_quarter816029]
growth=round((v1-v2)/v2,3)
this_quarter816029_item_list.append(v1)
last_quarter816029_item_list.append(v2)
ComparedWithTheSamePeriodList.append(growth)
# print(ComparedWithTheSamePeriodList)
FinancialData[this_quarter816029]=this_quarter816029_item_list
FinancialData[last_quarter816029]=last_quarter816029_item_list
FinancialData['YoY']=ComparedWithTheSamePeriodList # 添加同比列
# 较上一年变化情况
def CompareLastYear(t):
r1 = RecentYhreeQuarter[2] # 最近第一个报告期的列索引
r2 = RecentYhreeQuarter[1] # 最近第二个报告期的列索引
change = round((t[r1] - t[r2]) / t[r2], 3)
return change
FinancialData['CompareLastYear'] = FinancialData.apply(CompareLastYear, axis=1)
def LittleChange(t, p): # 是否基本持平列
'''
:param t: 好像表示DataFrame
:param p: 三期数据是否在平均的正负浮动数值范围内,暂时将参数值设置为0.08
:return: 返回布尔值,True 表示基本持平,NaN 表示否
'''
r1 = RecentYhreeQuarter[2] # 最近第一个报告期的列索引
r2 = RecentYhreeQuarter[1] # 最近第二个报告期的列索引
r3 = RecentYhreeQuarter[0] # 最近第三个报告期的列索引
average = (t[r1] + t[r2] + t[r3]) / 3
if (t[r1] > 0 and t[r2] > 0 and t[r3] > 0) and \
((t[r2] > t[r3] and t[r2] > t[r1]) or (t[r2] < t[r3] and t[r2] < t[r1])) and \
(t[r2] < (1 + p) * average and t[r2] > (1 - p) * average):
return '基本持平[正]'
elif (t[r1] < 0 and t[r2] < 0 and t[r3] < 0) and \
((t[r2] > t[r3] and t[r2] > t[r1]) or (t[r2] < t[r3] and t[r2] < t[r1])) and \
(t[r2] > (1 + p) * average and t[r2] < (1 - p) * average):
return '基本持平[负]'
else:
return np.NaN
FinancialData['LittleChange'] = FinancialData.apply(LittleChange, p=0.08, axis=1)
def Convex(t, p): # 凸型变化
r1 = RecentYhreeQuarter[2] # 最近第一个报告期的列索引
r2 = RecentYhreeQuarter[1] # 最近第二个报告期的列索引
r3 = RecentYhreeQuarter[0] # 最近第三个报告期的列索引
average = (t[r1] + t[r2] + t[r3]) / 3
if (t[r1] > 0 and t[r2] > 0 and t[r3] > 0) and (
t[r2] > t[r1] and t[r2] > t[r3] and t[r2] > (1 + p) * average):
return '正凸'
elif (t[r1] < 0 and t[r2] < 0 and t[r3] < 0) and (
t[r2] < t[r1] and t[r2] < t[r3] and t[r2] < (1 + p) * average):
return '负凸'
else:
return np.NaN
FinancialData['Convex'] = FinancialData.apply(Convex, p=0.08, axis=1)
def Concave(t, p): # 凹型变化
r1 = RecentYhreeQuarter[2] # 最近第一个报告期的列索引
r2 = RecentYhreeQuarter[1] # 最近第二个报告期的列索引
r3 = RecentYhreeQuarter[0] # 最近第三个报告期的列索引
average = (t[r1] + t[r2] + t[r3]) / 3
if (t[r1] > 0 and t[r2] > 0 and t[r3] > 0) and (
t[r2] < t[r1] and t[r2] < t[r3] and t[r2] < (1 - p) * average):