未完成项目:股票分析系统MyFunction23_05_18.py

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):
                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['Concave'] = FinancialData.apply(Concave, p=0.08, axis=1)

        def StickOut(t, p):  # 个别年份特别突出,即鹤立鸡群
            r1 = RecentYhreeQuarter[2]  # 最近第一个报告期的列索引
            r2 = RecentYhreeQuarter[1]  # 最近第二个报告期的列索引
            r3 = RecentYhreeQuarter[0]  # 最近第三个报告期的列索引
            sum_value = t[r1] + t[r2] + t[r3]
            list1 = [r1, r2, r3]
            bool_list = []
            for i in list1:
                if t[i] > 0 and abs(t[i]) > abs(p * (sum_value - t[i])):
                    bool_list.append('正突')
                elif t[i] < 0 and abs(t[i]) > abs(p * (sum_value - t[i])):
                    bool_list.append('负突')

            if '正突' in bool_list:
                return '正突'
            elif '负突' in bool_list:
                return '负突'
            else:
                return np.NaN

        FinancialData['StickOut'] = FinancialData.apply(StickOut, p=1.5, axis=1)

        def Minus1(t):  # 最近一年内是否为负值
            r1 = RecentYhreeQuarter[2]  # 最近第一个报告期的列索引
            if t[r1] < 0:
                return True
            else:
                return np.NaN

        FinancialData['Minus1'] = FinancialData.apply(Minus1, axis=1)

        def Minus2(t):  # 最近两年内是否为负值
            r1 = RecentYhreeQuarter[2]  # 最近第一个报告期的列索引
            r2 = RecentYhreeQuarter[1]  # 最近第二个报告期的列索引
            if t[r1] < 0 and t[r2] < 0:
                return True
            else:
                return np.NaN

        FinancialData['Minus2'] = FinancialData.apply(Minus2, axis=1)

        def Minus3(t):  # 最近三年内是否为负值
            r1 = RecentYhreeQuarter[2]  # 最近第一个报告期的列索引
            r2 = RecentYhreeQuarter[1]  # 最近第二个报告期的列索引
            r3 = RecentYhreeQuarter[0]  # 最近第三个报告期的列索引
            if t[r1] < 0 and t[r2] < 0 and t[r3] < 0:
                return True
            else:
                return np.NaN

        FinancialData['Minus3'] = FinancialData.apply(Minus3, axis=1)

        def StopLoss(t):  # 是否扭亏为盈
            r1 = RecentYhreeQuarter[2]  # 最近第一个报告期的列索引
            r2 = RecentYhreeQuarter[1]  # 最近第二个报告期的列索引
            if t[r2] < 0 and t[r1] > 0:
                return True
            else:
                return np.NaN

        FinancialData['StopLoss'] = FinancialData.apply(StopLoss, axis=1)

        def StopProfit(t):  # 是否盈利转亏损
            r1 = RecentYhreeQuarter[2]  # 最近第一个报告期的列索引
            r2 = RecentYhreeQuarter[1]  # 最近第二个报告期的列索引
            if t[r2] > 0 and t[r1] < 0:
                return True
            else:
                return np.NaN

        FinancialData['StopProfit'] = FinancialData.apply(StopProfit, axis=1)

        def ProfitLessLoss(t):  # 是否盈不抵亏
            r1 = RecentYhreeQuarter[2]  # 最近第一个报告期的列索引
            r2 = RecentYhreeQuarter[1]  # 最近第二个报告期的列索引
            r3 = RecentYhreeQuarter[0]  # 最近第三个报告期的列索引
            sum_value = t[r1] + t[r2] + t[r3]
            if sum_value < 0:
                return True
            else:
                return np.NaN

        FinancialData['ProfitLessLoss'] = FinancialData.apply(ProfitLessLoss, axis=1)

        def IsDataDrop(t):  # 是否存在数据缺失情况
            r1 = RecentYhreeQuarter[2]  # 最近第一个报告期的列索引
            r2 = RecentYhreeQuarter[1]  # 最近第二个报告期的列索引
            r3 = RecentYhreeQuarter[0]  # 最近第三个报告期的列索引
            list1 = [r1, r2, r3]
            bool_list = []
            for i in list1:
                if np.isnan(t[i]):
                    bool_list.append(True)
                else:
                    bool_list.append(False)
            if False not in bool_list:
                return True
            else:
                return np.NaN

        FinancialData['IsDataDrop'] = FinancialData.apply(IsDataDrop, axis=1)

        def OtherForm(t):

            bool_list = []
            # 如果‘ThreeChange’列中数据是nan,bool_list中添加True,否则为Flase
            if len(t['ThreeChange']) > 2:
                bool_list.append(True)
            else:
                bool_list.append(False)
            # 使用循环判断其他列
            colunms_list = ['LittleChange', 'Convex', 'Concave', 'StickOut', 'Minus1', 'Minus2', 'Minus3', 'StopLoss',
                            'StopProfit', 'ProfitLessLoss']
            for i in colunms_list:
                if t[i] == False:
                    bool_list.append(True)
                else:
                    bool_list.append(False)
            # 通过判断布尔索引列表中是否存在Flase,来判断返回值
            if False not in bool_list:
                return True
            else:
                return np.nan

        FinancialData['OtherForm'] = FinancialData.apply(OtherForm, axis=1)

        my_save_path_str=f'/Financial/FinancialItemAnalysis/财务数据分析{statement_type}-{item}-{report_type}.csv'
        final_save_path_str=Basics.PandasSaveData(data=FinancialData,path_suffix=my_save_path_str,index=True)

        end_time = datetime.datetime.now()
        print(end_time - start_time)



        '''
            列名                     含义                           备注
        ThreeChange         三年连续变化幅度                    指连续增长或下降,其中“NO”表示数据不是三连连续增长或降低类型,
                                                             即不该股票的不是三年连续变化的类型
        YoY                 最近一个季度指标的同比增长情况
        CompareLastYear     最近一年的值较最近二年的值变化情况
        Minus1              最近一年为负                       True表示是,nan或csv中空值表示否,其他列同此含义
        Minus2              最近两年为负
        Minus3              最近三年为负
        StopLoss            扭亏为盈                           最近第二数据为负,最近第一年数据转为正值
        StopProfit          盈利转亏损                         含义同“扭亏为盈”相反
        ProfitLessLoss      三年盈不抵亏                        三年中,数据的总和小于0,即为负值
        IsDataDrop          近三年数据不全                      是否数据缺失,即近三年数据不全
        LittleChange        基本持平                           英语含义:变化不大即基本持平的意思。“基本持平[正]”表示三年数值均为正值,
                                                              且中间年份的数据比两边大或小,但不属于凹凸形态;“基本持平[负]”的含义相反
        Convex              凸型变化                           “正凸”表示,连续三年数据为正值,且形态是两边低中间高;“负凸”含义相反。
        Concave             凹型变化                           关于“正凹”和“负凹”的解释,类似“正凸”和“负凸”
        StickOut            鹤立鸡群                           个别年份的数据明显比其他年份大,在形状上突出很多。“正突”表示有一年数据
                                                              为正且明显比其他两大很多,“负突”含义相反。
        OtherForm           其他形态                            除“CompareLastYear” 列,“IsDataDrop” 列外,只要不符合以上所形态
                                                              判断情况的其他形态,纳入本列                         
        '''
        # 7.函数返回值
        return (FinancialData,final_save_path_str)

    @classmethod
    def JudgeFinancialItemGrowth(cls,ts_code, data, compare_value,yoy):
        '''
        :param ts_code: 股票的ts码
        :param data: 财务指标分析数据
        :param compare_value:增长的对比值,比如compare_value=0.3,表示增长率是否大于30%
        :return: 如果指标增长率大于参数compare_value,则返回增长率的值,否则nan
        '''
        v = data.loc[ts_code, 'ThreeChange']  # data中'ThreeChange'列中的相应股票的数值
        g = data.loc[ts_code,'YoY']
        # print(v)
        if g>=yoy:
            if v != "NO":
                if v[0:4] == '数值为正':
                    if eval(v[10:len(v)]) > compare_value:
                        return eval(v[10:len(v)])
                    else:  # 增长率小于某个值,则返回此结果
                        return np.NaN
                else:  # v的值包含"数值为负",则返回此结果
                    return np.nan
            else:  # v的值为"NO",则返回此结果
                return np.NaN
        else: # 同比增长率小于参数yoy的值,返回此结果
            return np.NaN
    @classmethod
    def RecentlQuarterChange(cls,statement_type, item,is_year_report=False):  # 这是一个优秀的函数
        '''
        :param statement_type: 财务报表或财务指标的类型,参数值的含义:
                                income  利润表
                                balancesheet    资产负债表
                                cashflow    现金流量表
                                fina_indicator  财务指标
        :param item: 利润表、资产负债表、现金流量表或财务指标数据接口中输出参数中对应的参数名称
        :param is_year_report: 参考内部函数recent_report_data中参数解释
        :return: 返回一个dataframe,含有每两个上市公司同比增长情况数据及申万三级行业所有上市公司指标同比增长情况
        '''
        # 1.读取数据

        if statement_type == 'fina_indicator':
            file_path789125698, _ = Financial.FinaIndicator(item=item)
        else:
            file_path789125698, _ = Financial.FinancialItem(statement_type=statement_type, item=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)

        # 2023年6月11日升级代码
        NoDropFinancialData = pd.read_csv(file_path789125698)
        NoDropFinancialData.index = [i for i in NoDropFinancialData['ts_code'].values]

        # # 定义当前季度报告期

        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)

        # 向申万行业数据中添加同期对比列相关数据
        # TODO 是否可以将同期对比数据,修改为已有的数据同期对比。因为每季度后一个月内均为上市公司公布财务报表的合法期限。
        #  这一个月内,有的公司公布的财务报表早,有的晚,有的公司财务数据已经是第二季度数年,而有的公司仍然是第一季度的数据
        sw_data = BasicData.Get_Whole_SW()

        ComparedWithTheSamePeriodList = []
        this_quarter816029_item_list = []
        last_quarter816029_item_list = []

        for i in sw_data['ts_code'].values:
            try:
                v1 = NoDropFinancialData.loc[i, this_quarter816029]
                v2 = NoDropFinancialData.loc[i, last_quarter816029]
                # print(v1,v2)
                growth = round((v1 - v2) / v2, 3)
                this_quarter816029_item_list.append(v1)
                last_quarter816029_item_list.append(v2)
                ComparedWithTheSamePeriodList.append(growth)
            except:
                # print(np.NaN,np.NaN)
                this_quarter816029_item_list.append(np.NaN)
                last_quarter816029_item_list.append(np.NaN)
                ComparedWithTheSamePeriodList.append(np.NaN)
        # print(ComparedWithTheSamePeriodList)
        sw_data[this_quarter816029] = this_quarter816029_item_list
        sw_data[last_quarter816029] = last_quarter816029_item_list
        sw_data['YoY'] = ComparedWithTheSamePeriodList  # 添加同比列

        # 增加“是否为正增长”和“是否负增长”列
        def positive_growth(t):  # 是否为正增长
            if np.isnan(t['YoY']):
                return np.NaN
            else:
                if t['YoY'] >= 0:
                    return True
                else:
                    return np.NaN

        def minus_growth(t):  # 是否为负增长
            if np.isnan(t['YoY']):
                return np.NaN
            else:
                if t['YoY'] < 0:
                    return True
                else:
                    return np.NaN

        sw_data['positive_growth'] = sw_data.apply(positive_growth, axis=1)
        sw_data['minus_growth'] = sw_data.apply(minus_growth, axis=1)

        group_data = sw_data.groupby(by=['industry_name1', 'industry_name2', 'industry_name3'])
        positive_growth_num = group_data['positive_growth'].count()
        minus_growth_num = group_data['minus_growth'].count()

        # 多个Series创建dataframe

        result = pd.DataFrame({'positive_num': positive_growth_num, 'minus_num': minus_growth_num})

        # 添加同时显示正增长数和负增长数列
        def MergeDisplay(t):  # 合并显示
            # return t['positive_num']+'/'+t['minus_num']
            return '[{}/{}]'.format(t['positive_num'], t['minus_num'])

        result['MergeDisplay'] = result.apply(MergeDisplay, axis=1)
        # 将复合索引中的数据转化成列
        name3_list = []
        for i in result.index:
            _, _, name3 = i
            name3_list.append(name3)
        result['name3'] = name3_list
        # 重新将设置列索引
        result.index = [i for i in result['name3'].values]


        # 添加申万三级行业最近季度指标增长情况列
        sw_data.index=[i for i in sw_data['ts_code'].values]
        sw_lv3_growth_list=[]
        for i in sw_data['ts_code'].values:
            sw_name3=sw_data.loc[i,'industry_name3']
            try:
                sw_lv3_growth = result.loc[sw_name3, 'MergeDisplay']
                sw_lv3_growth_list.append(sw_lv3_growth)
            except:
                sw_lv3_growth_list.append(np.NaN)
        sw_data['sw_lv3_growth']=sw_lv3_growth_list
        '''
            列名称说明
            YoY                 指标同比增长情况
            positive_growth     指标是否同比正增长
            minus_growth        指标是否同比负增长
            sw_lv3_growth       关于最近季度财务报表或财务指标数据的增长情况,比如[8/2],表示该股票所在申万三级行业中
                                存在统计数据的股票总数为8+2=10个,其中正增长个数为8个,负增长个数为2个
        '''
        # # 函数返回值
        return sw_data


    @classmethod
    def Forecast(cls,start_date, end_date, Quarter,is_derive_original_data=True):
        '''
        2023年6月18日完成封装
        :param start_date: 预测的开始日期
        :param end_date: 预测的结束日期
        :param Quarter: 预测的报告期,比如2022Q4表示2022年4季度
        :return:
        '''
        path2 = '/Financial/Forecast/机构预测数据的统计结果.csv'
        bool_value,final_save_path_str=Basics.IsNewDataFile(path_suffix=path2)
        if bool_value: # 增加一个逻辑判断,如果是最新文件,则不需要重新生成文件
            print('已是最新文件,无需更新!')
        else:
            # 定义将日期字符串转化为日期的函数
            def turn_str_to_date(date_str):
                y_int = eval(date_str[0:4])
                m_int = eval(date_str[4:6].strip('0'))
                d_int = eval(date_str[6:8].strip('0'))

                turn_date = datetime.date(year=y_int, month=m_int, day=d_int)

                return turn_date

            # 使用定义的内部函数
            start = turn_str_to_date(date_str=start_date)
            end = turn_str_to_date(date_str=end_date)
            # 创建日期列表
            date_list = []
            mydate = start
            date_list.append(start_date)
            for i in range(12500):
                mydate = mydate + datetime.timedelta(days=1)
                if mydate < end:
                    mydate_str = mydate.__str__()
                    new_date_str = mydate_str.replace('-', '')
                    date_list.append(new_date_str)
                elif mydate == end:
                    mydate_str = mydate.__str__()
                    new_date_str = mydate_str.replace('-', '')
                    date_list.append(new_date_str)
                    break

            # 4.获取预测数据
            forecast_df = pd.DataFrame()
            for i in date_list:
                # print(i)
                print('正在获取日期{}机构的预测数据......'.format(i))
                df = pro.report_rc(ts_code='', report_date=i)
                forecast_df = pd.concat([forecast_df, df])
            forecast_df.index = [i for i in range(forecast_df['ts_code'].count())]

            # 如果参数is_derive_original_data的值为True,则导出原始预测数据
            path1 = '/Financial/Forecast/机构预测的原始数据.csv' # TODO 20230621升级前的代码
            if is_derive_original_data==True:
                Basics.PandasSaveData(data=forecast_df,path_suffix=path1)
            else:
                pass

            def forecast_statistics(ts_code, quarter):
                import numpy as np
                df = forecast_df[(forecast_df['ts_code'] == ts_code) & (forecast_df['quarter'] == quarter)]
                '''
                代码说明:
                    从原始数据库forecast_df中筛选数据,筛选条件:
                    df = forecast_df[(forecast_df['ts_code'] == ts_code) & (forecast_df['quarter'] == quarter)]
                '''
                # print(df)
                if df['ts_code'].count() == 0:
                    forecast_num = np.NaN
                else:
                    forecast_num = df['ts_code'].count()

                op_rt = round(10000*df['op_rt'].mean(), 0)
                np = round(10000*df['np'].mean(), 0)

                dict1 = {'ts_code': ts_code,
                         'forecast_num': forecast_num,
                         # 'rating': rating_level,
                         'op_rt': op_rt,
                         'np': np}
                return dict1

            data = pro.stock_basic(exchange='', list_status='L', fields='ts_code,name')
            statistics_list = []
            for i in data['ts_code'].values: # TODO 此循环运行停耗费时间,待以后测试
                print('正在计算{}预测期{}的统计数据......'.format(i,Quarter))
                dict1 = forecast_statistics(ts_code=i, quarter=Quarter)
                statistics_list.append(dict1)
            result_df = pd.DataFrame(statistics_list)

            # 获取三个日期的函数
            def get_three_date(my_quarter):
                '''
                :param my_quarter: 预测的报告期,比如2022Q4表示2022年4季度
                :return: 返回一个元组包含三个日期:
                            last_report_date        表示对应最近一个报告期的上一个报告期,比如今天为2023年6月17日,最近一个年报的报告期为20221231,则上一个报告期为20211231
                            this_report_date        表示最近的一个报告期20221231,比如今天为2023年6月17日,最近一个年报的报告期为20221231。
                            forecast_date           表示要预测的报告日期20231231,比如今天为2023年6月17日,现在实际未到这个日期。
                '''
                if my_quarter[-1] == '4':
                    forecast_date = my_quarter[0:4] + '1231'
                    this_report_date = str(eval(my_quarter[0:4]) - 1) + '1231'
                    last_report_date = str(eval(my_quarter[0:4]) - 2) + '1231'
                    return (last_report_date, this_report_date, forecast_date)
                elif my_quarter[-1] == '3':
                    forecast_date = my_quarter[0:4] + '0930'
                    this_report_date = str(eval(my_quarter[0:4]) - 1) + '0930'
                    last_report_date = str(eval(my_quarter[0:4]) - 2) + '0930'
                    return (last_report_date, this_report_date, forecast_date)
                elif my_quarter[-1] == '2':
                    forecast_date = my_quarter[0:4] + '0630'
                    this_report_date = str(eval(my_quarter[0:4]) - 1) + '0630'
                    last_report_date = str(eval(my_quarter[0:4]) - 2) + '0630'
                    return (last_report_date, this_report_date, forecast_date)

                elif my_quarter[-1] == '1':
                    forecast_date = my_quarter[0:4] + '0331'
                    this_report_date = str(eval(my_quarter[0:4]) - 1) + '0331'
                    last_report_date = str(eval(my_quarter[0:4]) - 2) + '0331'
                    return (last_report_date, this_report_date, forecast_date)

            result_df.index = [i for i in result_df['ts_code'].values]
            last_report_date20230617, this_report_date20230617,period = get_three_date(my_quarter=Quarter)

            # 添加实际营业总收入列

            last_total_revenue_data, _ = Financial.PeriodFinancial(statement_type='income', item='total_revenue', period=last_report_date20230617)
            this_total_revenue_data, _ = Financial.PeriodFinancial(statement_type='income', item='total_revenue', period=this_report_date20230617)
            total_revenue_data, _ = Financial.PeriodFinancial(statement_type='income', item='total_revenue', period=period)
            last_total_revenue_list=[] # 上一个报告期的营收
            this_total_revenue_list=[] # 最近一个报告期的营收
            forecast_total_revenue_list = [] # 预测的报告期的营收
            # ---'获取上一个报告期的营收'---
            for i in result_df['ts_code'].values:
                if np.isnan(result_df.loc[i, 'forecast_num']):
                    last_total_revenue_list.append(np.NaN)
                else:
                    try:
                        last_total_revenue=last_total_revenue_data.loc[i,last_report_date20230617]
                        last_total_revenue_list.append(last_total_revenue)

                    except:
                        last_total_revenue_list.append(np.NaN)

            # ---'获取最近报告期的营收'---
            for i in result_df['ts_code'].values:
                if np.isnan(result_df.loc[i, 'forecast_num']):
                    this_total_revenue_list.append(np.NaN)

                else:
                    try:
                        this_total_revenue=this_total_revenue_data.loc[i,this_report_date20230617]
                        this_total_revenue_list.append(this_total_revenue)

                    except:
                        this_total_revenue_list.append(np.NaN)
            #  ---'预测期的营收'---
            for i in result_df['ts_code'].values:
                if np.isnan(result_df.loc[i, 'forecast_num']):
                    forecast_total_revenue_list.append(np.NaN)

                else:
                    try:
                        total_revenue = total_revenue_data.loc[i, period]
                        forecast_total_revenue_list.append(total_revenue)

                    except:
                        forecast_total_revenue_list.append(np.NaN)
            result_df['last_t']=last_total_revenue_list
            result_df['this_t']=this_total_revenue_list
            result_df['fore_t'] = forecast_total_revenue_list
            # 添加归母净利润列

            last_n_income_attr_p_data, _ = Financial.PeriodFinancial(statement_type='income', item='n_income_attr_p',
                                                                period=last_report_date20230617)
            this_n_income_attr_p_data, _ = Financial.PeriodFinancial(statement_type='income', item='n_income_attr_p',
                                                                period=this_report_date20230617)
            n_income_attr_p_data, _ = Financial.PeriodFinancial(statement_type='income', item='n_income_attr_p',
                                                                period=period)


            last_n_income_attr_p_list=[]
            this_n_income_attr_p_list=[]
            n_income_attr_p_list = []
            # ---获取上一个报告期的归母净利润---
            for i in result_df['ts_code'].values:
                if np.isnan(result_df.loc[i, 'forecast_num']):
                    last_n_income_attr_p_list.append(np.NaN)
                else:
                    try:
                        last_n_income_attr_p=last_n_income_attr_p_data.loc[i,last_report_date20230617]
                        last_n_income_attr_p_list.append(last_n_income_attr_p)
                    except:
                        last_n_income_attr_p_list.append(np.NaN)
            # ---获取最近一个报告期的归母净利润---
            for i in result_df['ts_code'].values:
                if np.isnan(result_df.loc[i, 'forecast_num']):
                    this_n_income_attr_p_list.append(np.NaN)
                else:
                    try:
                        this_n_income_attr_p=this_n_income_attr_p_data.loc[i,this_report_date20230617]
                        this_n_income_attr_p_list.append(this_n_income_attr_p)

                    except:
                        this_n_income_attr_p_list.append(np.NaN)
            # ---获取预测期的归母净利润---
            for i in result_df['ts_code'].values:
                if np.isnan(result_df.loc[i, 'forecast_num']):
                    n_income_attr_p_list.append(np.NaN)
                else:
                    try:
                        n_income_attr_p = n_income_attr_p_data.loc[i, period]
                        n_income_attr_p_list.append(n_income_attr_p)
                    except:
                        n_income_attr_p_list.append(np.NaN)
            result_df['last_n']=last_n_income_attr_p_list
            result_df['this_n']=this_n_income_attr_p_list
            result_df['fore_n'] = n_income_attr_p_list

            # 添加修正后的预测总营业收入和修正后的预测归母净利润列
            dict1 = {'总营收修正系数': [20.85 / 100, 19.88 / 100, 18.63 / 100, 17.50 / 100, 15.93 / 100, 14.05 / 100,
                                        13.43 / 100, 12.77 / 100, 13.08 / 100],
                     '归母净利修正系数': [24.45 / 100, 26.58 / 100, 26.53 / 100, 26.30 / 100, 26.63 / 100, 24.44 / 100,
                                          23.36 / 100, 20.35 / 100, 18.23 / 100]}
            correction_factor_df = pd.DataFrame(data=dict1, index=['1', '5', '10', '20', '30', '50', '75', '100', '125'])
            '''
                以上数据来源于自己的研究结论,见资料:
                https://www.aliyundrive.com/s/n9F6Dj7Fjn9
                https://share.weiyun.com/fd54Wgcl
                研究发现:经过修正后的预测数据可能偏小,但可以防止机构高估总营业收入和归母净利润的问题。
            '''
            correct_op_rt_list = []
            correct_np_list = []
            for i in result_df['ts_code'].values:
                print('正在计算{}修正后的总营业收入和归母净利润......'.format(i))
                try:
                    y = result_df.loc[i, 'forecast_num']
                    global x1688991
                    if y >= 1 and y < 5:
                        x1688991 = '1'
                    elif y >= 5 and y < 10:
                        x1688991 = '5'
                    elif y >= 10 and y < 20:
                        x1688991 = '10'
                    elif y >= 20 and y < 30:
                        x1688991 = '20'
                    elif y >= 30 and y < 50:
                        x1688991 = '30'
                    elif y >= 50 and y < 75:
                        x1688991 = '50'
                    elif y >= 75 and y < 100:
                        x1688991 = '75'
                    elif y >= 100 and y < 125:
                        x1688991 = '100'
                    elif y >= 125:
                        x1688991 = '125'
                    x = x1688991

                    c_op_rt = correction_factor_df.loc[x, '总营收修正系数']
                    c_np = correction_factor_df.loc[x, '归母净利修正系数']

                    vp1 = round(result_df.loc[i, 'op_rt'] * (1 - c_op_rt), 0)  # 修正后的预测总营业收入
                    vp2 = round(result_df.loc[i, 'np'] * (1 - c_np), 0)  # 修正后的预测归母净利润

                    correct_op_rt_list.append(vp1)
                    correct_np_list.append(vp2)
                except:

                    correct_op_rt_list.append(np.NaN)
                    correct_np_list.append(np.NaN)

            result_df['correct_op_rt'] = correct_op_rt_list
            result_df['correct_np'] = correct_np_list
            # 从申万三级行业中被机机构预测的股票数量,分析机构对申万三行业的关注情况
            '''
                添加机构分析申万三级行业中,在分析时间段内,被机分析过的上市公司数量和该申万行业中上市公司的总数,[3/8]表示,某个股票所有
            的申万三行业,共有8家上市公司,机构预测了其中3家上市公司的数据。从这个数据,可以发现某个时段中,机构对某个申万三级行业的关注情况。
            '''
            sw_data = BasicData.Get_Whole_SW()
            sw_data.index = [i for i in sw_data['ts_code'].values]
            is_in_sw_list = [] # 判断股票是否被机构预测的列表
            for i in sw_data['ts_code'].values: # 如果预测数大于0,即不是nan,表明机构对该只股票有关注,本表添加元素1,否则,添加元素0
                if np.isnan(result_df.loc[i, 'forecast_num']):
                    is_in_sw_list.append(0)
                else:
                    is_in_sw_list.append(1)
            sw_data['is_in_sw'] = is_in_sw_list # 将is_in_sw_list中的数据转化为列
            # 构造一个dataframe,含有申万三级行业中股票数量,以及申万三级行业中被机构预测过的股票数量
            group_data = sw_data.groupby(by=['industry_name1', 'industry_name2', 'industry_name3'])
            total_sw_num = group_data['ts_code'].count() # 申万三级行业中股票数量
            forecast_sw_num = group_data['is_in_sw'].sum() # 申万三级行业中被机构预测过的股票数量
            sw_group_num = pd.DataFrame({'total_sw_num': total_sw_num,
                                         'forecast_sw_num': forecast_sw_num}) # 将多个Series数据合并为dataframe
            # 本段代码的功能,将sw_group_num的行索引设置为申万三级行业的名称
            name3_list = []
            for i in sw_group_num.index.values:
                _, _, name3 = i # sw_group_num行索引采用的是复合索引,其索引值中的元素为一个元组,因此需要使用元组的拆包功能获取申万三级行业的名称
                name3_list.append(name3)
            sw_group_num.index = name3_list
            # 设置显示被预测数/申万三级行业股票总数列
            symbol_sw_num_list = []

            for i in result_df['ts_code'].values:

                if np.isnan(result_df.loc[i, 'forecast_num']):
                    symbol_sw_num_list.append(np.NaN)
                else:
                    try:
                        sw_name_l3 = sw_data.loc[i, 'industry_name3']
                        h = sw_group_num.loc[sw_name_l3, 'forecast_sw_num']
                        l = sw_group_num.loc[sw_name_l3, 'total_sw_num']
                        symbol_sw_num_list.append('[{}/{}]'.format(h, l))
                    except:
                        symbol_sw_num_list.append(np.NaN)

            result_df['sw_num'] = symbol_sw_num_list
            '''
            该列数据含义:
                    添加机构分析申万三级行业中,在分析时间段内,被机分析过的上市公司数量和该申万行业中上市公司的总数,[3/8]表示,某个股票所有
                的申万三行业,共有8家上市公司,机构预测了其中3家上市公司的数据。从这个数据,可以发现某个时段中,机构对某个申万三级行业的关注情况。            
            '''
            # 添加增长率分析列
            def forecastgrowth(t,col1,col2):
                return round((t[col2]-t[col1])/t[col1],4)
            result_df['comp_t1']=result_df.apply(forecastgrowth,col1='last_t',col2='this_t',axis=1)
            result_df['comp_t2'] = result_df.apply(forecastgrowth, col1='this_t', col2='correct_op_rt', axis=1)
            result_df['comp_n1'] = result_df.apply(forecastgrowth, col1='last_n', col2='this_n', axis=1)
            result_df['comp_n2'] = result_df.apply(forecastgrowth, col1='this_n', col2='correct_np', axis=1)
            # 调整列顺序
            final_data=result_df[['ts_code','forecast_num','sw_num','last_t','last_n','this_t','this_n','op_rt','np','correct_op_rt','correct_np','comp_t1','comp_t2','comp_n1','comp_n2','fore_t','fore_n']]

            # 导出数据
            # path2 = '/Financial/Forecast/机构预测数据的统计结果.csv'
            Basics.PandasSaveData(data=final_data,path_suffix=path2)
            '''
            ts_code             股票代码
            forecast_num        在选定时间段内该股票被机构预测的次数
            sw_num              在选定时间段内,申万三级行业内被机构预测的股票数量/该行业的所有股票的数量
            last_t              上一个报告期的总营业收入
            last_n              上一个报告期的归母净利润
            this_t              最近一个报告期的总营业收入
            this_n              最近一个报告期的归母净利润
            op_rt               预测的总营业收入(元)
            np                  预测的归母净利润(元)
            correct_op_rt       修正后的预测的总营业收入(元)
            correct_np          修正后的预测的归母净利润(元)
            fore_t              预测期的总营业收入(元),如果预测期大于当前日期,则该数据为nan,导出时为空值。预测日期为已经发生的日期,用于检查机构的预测结果时才用。
            fore_n              预测期的归母净利润(元),如果预测期大于当前日期,则该数据为nan,导出时为空值。预测日期为已经发生的日期,用于检查机构的预测结果时才用。
            comp_t1             最近一期总营收相比上一期总营收的增长率
            comp_t2             预测期总营收相比最近一期总营收的增长率
            comp_n1             最近一期归母净利润相比上一期归母净利润的增长率
            comp_n2             预测期归母净利润相比最近一期归母净利润的增长率
            '''
    @classmethod
    def ReadRecentYearForecast(cls):
        '''
        本函数由Forecast()函数改进而来
        '''
        # 预期期为最近一年,预测目标期为当年第四季度,即年报的营收
        start_date = Today_Date_Count(-365)
        end_date = Today_Date_Count()
        Quarter = str(datetime.date.today().year) + 'Q4'
        is_derive_original_data = True

        path2 = '/Financial/ReadRecentYearForecast/最近一年机构预测数据的统计结果.csv'
        bool_value, final_save_path_str = Basics.IsNewDataFile(path_suffix=path2)
        if bool_value:  # 增加一个逻辑判断,如果是最新文件,则不需要重新生成文件
            print('已是最新文件,无需更新!')
            if final_save_path_str[-3:]=='csv':
                read_data1=pd.read_csv(final_save_path_str)
                return read_data1
            elif final_save_path_str[-4:]=='xlsx':
                read_data2=pd.read_excel(final_save_path_str)
                return read_data2

        else:
            # 定义将日期字符串转化为日期的函数
            def turn_str_to_date(date_str):
                y_int = eval(date_str[0:4])
                m_int = eval(date_str[4:6].strip('0'))
                d_int = eval(date_str[6:8].strip('0'))

                turn_date = datetime.date(year=y_int, month=m_int, day=d_int)

                return turn_date

            # 使用定义的内部函数
            start = turn_str_to_date(date_str=start_date)
            end = turn_str_to_date(date_str=end_date)
            # 创建日期列表
            date_list = []
            mydate = start
            date_list.append(start_date)
            for i in range(12500):
                mydate = mydate + datetime.timedelta(days=1)
                if mydate < end:
                    mydate_str = mydate.__str__()
                    new_date_str = mydate_str.replace('-', '')
                    date_list.append(new_date_str)
                elif mydate == end:
                    mydate_str = mydate.__str__()
                    new_date_str = mydate_str.replace('-', '')
                    date_list.append(new_date_str)
                    break

            # 4.获取预测数据
            forecast_df = pd.DataFrame()
            for i in date_list:
                # print(i)
                print('正在获取日期{}机构的预测数据......'.format(i))
                df = pro.report_rc(ts_code='', report_date=i)
                forecast_df = pd.concat([forecast_df, df])
            forecast_df.index = [i for i in range(forecast_df['ts_code'].count())]

            # 如果参数is_derive_original_data的值为True,则导出原始预测数据
            path1 = '/Financial/ReadRecentYearForecast/最近一年机构预测的原始数据.csv'  # TODO 20230621升级前的代码
            if is_derive_original_data == True:
                Basics.PandasSaveData(data=forecast_df, path_suffix=path1)
            else:
                pass

            def forecast_statistics(ts_code, quarter):
                import numpy as np
                df = forecast_df[(forecast_df['ts_code'] == ts_code) & (forecast_df['quarter'] == quarter)]
                '''
                代码说明:
                    从原始数据库forecast_df中筛选数据,筛选条件:
                    df = forecast_df[(forecast_df['ts_code'] == ts_code) & (forecast_df['quarter'] == quarter)]
                '''
                # print(df)
                if df['ts_code'].count() == 0:
                    forecast_num = np.NaN
                else:
                    forecast_num = df['ts_code'].count()

                op_rt = round(10000 * df['op_rt'].mean(), 0)
                np = round(10000 * df['np'].mean(), 0)

                dict1 = {'ts_code': ts_code,
                         'forecast_num': forecast_num,
                         # 'rating': rating_level,
                         'op_rt': op_rt,
                         'np': np}
                return dict1

            data = pro.stock_basic(exchange='', list_status='L', fields='ts_code,name')
            statistics_list = []
            for i in data['ts_code'].values:  # TODO 此循环运行停耗费时间,待以后测试
                print('正在计算{}预测期{}的统计数据......'.format(i, Quarter))
                dict1 = forecast_statistics(ts_code=i, quarter=Quarter)
                statistics_list.append(dict1)
            result_df = pd.DataFrame(statistics_list)

            # 获取三个日期的函数
            def get_three_date(my_quarter):
                '''
                :param my_quarter: 预测的报告期,比如2022Q4表示2022年4季度
                :return: 返回一个元组包含三个日期:
                            last_report_date        表示对应最近一个报告期的上一个报告期,比如今天为2023年6月17日,最近一个年报的报告期为20221231,则上一个报告期为20211231
                            this_report_date        表示最近的一个报告期20221231,比如今天为2023年6月17日,最近一个年报的报告期为20221231。
                            forecast_date           表示要预测的报告日期20231231,比如今天为2023年6月17日,现在实际未到这个日期。
                '''
                if my_quarter[-1] == '4':
                    forecast_date = my_quarter[0:4] + '1231'
                    this_report_date = str(eval(my_quarter[0:4]) - 1) + '1231'
                    last_report_date = str(eval(my_quarter[0:4]) - 2) + '1231'
                    return (last_report_date, this_report_date, forecast_date)
                elif my_quarter[-1] == '3':
                    forecast_date = my_quarter[0:4] + '0930'
                    this_report_date = str(eval(my_quarter[0:4]) - 1) + '0930'
                    last_report_date = str(eval(my_quarter[0:4]) - 2) + '0930'
                    return (last_report_date, this_report_date, forecast_date)
                elif my_quarter[-1] == '2':
                    forecast_date = my_quarter[0:4] + '0630'
                    this_report_date = str(eval(my_quarter[0:4]) - 1) + '0630'
                    last_report_date = str(eval(my_quarter[0:4]) - 2) + '0630'
                    return (last_report_date, this_report_date, forecast_date)

                elif my_quarter[-1] == '1':
                    forecast_date = my_quarter[0:4] + '0331'
                    this_report_date = str(eval(my_quarter[0:4]) - 1) + '0331'
                    last_report_date = str(eval(my_quarter[0:4]) - 2) + '0331'
                    return (last_report_date, this_report_date, forecast_date)

            result_df.index = [i for i in result_df['ts_code'].values]
            last_report_date20230617, this_report_date20230617, period = get_three_date(my_quarter=Quarter)

            # 添加实际营业总收入列

            last_total_revenue_data, _ = Financial.PeriodFinancial(statement_type='income', item='total_revenue',
                                                                   period=last_report_date20230617)
            this_total_revenue_data, _ = Financial.PeriodFinancial(statement_type='income', item='total_revenue',
                                                                   period=this_report_date20230617)
            total_revenue_data, _ = Financial.PeriodFinancial(statement_type='income', item='total_revenue',
                                                              period=period)
            last_total_revenue_list = []  # 上一个报告期的营收
            this_total_revenue_list = []  # 最近一个报告期的营收
            forecast_total_revenue_list = []  # 预测的报告期的营收
            # ---'获取上一个报告期的营收'---
            for i in result_df['ts_code'].values:
                if np.isnan(result_df.loc[i, 'forecast_num']):
                    last_total_revenue_list.append(np.NaN)
                else:
                    try:
                        last_total_revenue = last_total_revenue_data.loc[i, last_report_date20230617]
                        last_total_revenue_list.append(last_total_revenue)

                    except:
                        last_total_revenue_list.append(np.NaN)

            # ---'获取最近报告期的营收'---
            for i in result_df['ts_code'].values:
                if np.isnan(result_df.loc[i, 'forecast_num']):
                    this_total_revenue_list.append(np.NaN)

                else:
                    try:
                        this_total_revenue = this_total_revenue_data.loc[i, this_report_date20230617]
                        this_total_revenue_list.append(this_total_revenue)

                    except:
                        this_total_revenue_list.append(np.NaN)
            #  ---'预测期的营收'---
            for i in result_df['ts_code'].values:
                if np.isnan(result_df.loc[i, 'forecast_num']):
                    forecast_total_revenue_list.append(np.NaN)

                else:
                    try:
                        total_revenue = total_revenue_data.loc[i, period]
                        forecast_total_revenue_list.append(total_revenue)

                    except:
                        forecast_total_revenue_list.append(np.NaN)
            result_df['last_t'] = last_total_revenue_list
            result_df['this_t'] = this_total_revenue_list
            result_df['fore_t'] = forecast_total_revenue_list
            # 添加归母净利润列

            last_n_income_attr_p_data, _ = Financial.PeriodFinancial(statement_type='income', item='n_income_attr_p',
                                                                     period=last_report_date20230617)
            this_n_income_attr_p_data, _ = Financial.PeriodFinancial(statement_type='income', item='n_income_attr_p',
                                                                     period=this_report_date20230617)
            n_income_attr_p_data, _ = Financial.PeriodFinancial(statement_type='income', item='n_income_attr_p',
                                                                period=period)

            last_n_income_attr_p_list = []
            this_n_income_attr_p_list = []
            n_income_attr_p_list = []
            # ---获取上一个报告期的归母净利润---
            for i in result_df['ts_code'].values:
                if np.isnan(result_df.loc[i, 'forecast_num']):
                    last_n_income_attr_p_list.append(np.NaN)
                else:
                    try:
                        last_n_income_attr_p = last_n_income_attr_p_data.loc[i, last_report_date20230617]
                        last_n_income_attr_p_list.append(last_n_income_attr_p)
                    except:
                        last_n_income_attr_p_list.append(np.NaN)
            # ---获取最近一个报告期的归母净利润---
            for i in result_df['ts_code'].values:
                if np.isnan(result_df.loc[i, 'forecast_num']):
                    this_n_income_attr_p_list.append(np.NaN)
                else:
                    try:
                        this_n_income_attr_p = this_n_income_attr_p_data.loc[i, this_report_date20230617]
                        this_n_income_attr_p_list.append(this_n_income_attr_p)

                    except:
                        this_n_income_attr_p_list.append(np.NaN)
            # ---获取预测期的归母净利润---
            for i in result_df['ts_code'].values:
                if np.isnan(result_df.loc[i, 'forecast_num']):
                    n_income_attr_p_list.append(np.NaN)
                else:
                    try:
                        n_income_attr_p = n_income_attr_p_data.loc[i, period]
                        n_income_attr_p_list.append(n_income_attr_p)
                    except:
                        n_income_attr_p_list.append(np.NaN)
            result_df['last_n'] = last_n_income_attr_p_list
            result_df['this_n'] = this_n_income_attr_p_list
            result_df['fore_n'] = n_income_attr_p_list

            # 添加修正后的预测总营业收入和修正后的预测归母净利润列
            dict1 = {'总营收修正系数': [20.85 / 100, 19.88 / 100, 18.63 / 100, 17.50 / 100, 15.93 / 100, 14.05 / 100,
                                        13.43 / 100, 12.77 / 100, 13.08 / 100],
                     '归母净利修正系数': [24.45 / 100, 26.58 / 100, 26.53 / 100, 26.30 / 100, 26.63 / 100, 24.44 / 100,
                                          23.36 / 100, 20.35 / 100, 18.23 / 100]}
            correction_factor_df = pd.DataFrame(data=dict1,
                                                index=['1', '5', '10', '20', '30', '50', '75', '100', '125'])
            '''
                以上数据来源于自己的研究结论,见资料:
                https://www.aliyundrive.com/s/n9F6Dj7Fjn9
                https://share.weiyun.com/fd54Wgcl
                研究发现:经过修正后的预测数据可能偏小,但可以防止机构高估总营业收入和归母净利润的问题。
            '''
            correct_op_rt_list = []
            correct_np_list = []
            for i in result_df['ts_code'].values:
                print('正在计算{}修正后的总营业收入和归母净利润......'.format(i))
                try:
                    y = result_df.loc[i, 'forecast_num']
                    global x1688991
                    if y >= 1 and y < 5:
                        x1688991 = '1'
                    elif y >= 5 and y < 10:
                        x1688991 = '5'
                    elif y >= 10 and y < 20:
                        x1688991 = '10'
                    elif y >= 20 and y < 30:
                        x1688991 = '20'
                    elif y >= 30 and y < 50:
                        x1688991 = '30'
                    elif y >= 50 and y < 75:
                        x1688991 = '50'
                    elif y >= 75 and y < 100:
                        x1688991 = '75'
                    elif y >= 100 and y < 125:
                        x1688991 = '100'
                    elif y >= 125:
                        x1688991 = '125'
                    x = x1688991

                    c_op_rt = correction_factor_df.loc[x, '总营收修正系数']
                    c_np = correction_factor_df.loc[x, '归母净利修正系数']

                    vp1 = round(result_df.loc[i, 'op_rt'] * (1 - c_op_rt), 0)  # 修正后的预测总营业收入
                    vp2 = round(result_df.loc[i, 'np'] * (1 - c_np), 0)  # 修正后的预测归母净利润

                    correct_op_rt_list.append(vp1)
                    correct_np_list.append(vp2)
                except:

                    correct_op_rt_list.append(np.NaN)
                    correct_np_list.append(np.NaN)

            result_df['correct_op_rt'] = correct_op_rt_list
            result_df['correct_np'] = correct_np_list
            # 从申万三级行业中被机机构预测的股票数量,分析机构对申万三行业的关注情况
            '''
                添加机构分析申万三级行业中,在分析时间段内,被机分析过的上市公司数量和该申万行业中上市公司的总数,[3/8]表示,某个股票所有
            的申万三行业,共有8家上市公司,机构预测了其中3家上市公司的数据。从这个数据,可以发现某个时段中,机构对某个申万三级行业的关注情况。
            '''
            sw_data = BasicData.Get_Whole_SW()
            sw_data.index = [i for i in sw_data['ts_code'].values]
            is_in_sw_list = []  # 判断股票是否被机构预测的列表
            for i in sw_data['ts_code'].values:  # 如果预测数大于0,即不是nan,表明机构对该只股票有关注,本表添加元素1,否则,添加元素0
                if np.isnan(result_df.loc[i, 'forecast_num']):
                    is_in_sw_list.append(0)
                else:
                    is_in_sw_list.append(1)
            sw_data['is_in_sw'] = is_in_sw_list  # 将is_in_sw_list中的数据转化为列
            # 构造一个dataframe,含有申万三级行业中股票数量,以及申万三级行业中被机构预测过的股票数量
            group_data = sw_data.groupby(by=['industry_name1', 'industry_name2', 'industry_name3'])
            total_sw_num = group_data['ts_code'].count()  # 申万三级行业中股票数量
            forecast_sw_num = group_data['is_in_sw'].sum()  # 申万三级行业中被机构预测过的股票数量
            sw_group_num = pd.DataFrame({'total_sw_num': total_sw_num,
                                         'forecast_sw_num': forecast_sw_num})  # 将多个Series数据合并为dataframe
            # 本段代码的功能,将sw_group_num的行索引设置为申万三级行业的名称
            name3_list = []
            for i in sw_group_num.index.values:
                _, _, name3 = i  # sw_group_num行索引采用的是复合索引,其索引值中的元素为一个元组,因此需要使用元组的拆包功能获取申万三级行业的名称
                name3_list.append(name3)
            sw_group_num.index = name3_list
            # 设置显示被预测数/申万三级行业股票总数列
            symbol_sw_num_list = []

            for i in result_df['ts_code'].values:

                if np.isnan(result_df.loc[i, 'forecast_num']):
                    symbol_sw_num_list.append(np.NaN)
                else:
                    try:
                        sw_name_l3 = sw_data.loc[i, 'industry_name3']
                        h = sw_group_num.loc[sw_name_l3, 'forecast_sw_num']
                        l = sw_group_num.loc[sw_name_l3, 'total_sw_num']
                        symbol_sw_num_list.append('[{}/{}]'.format(h, l))
                    except:
                        symbol_sw_num_list.append(np.NaN)

            result_df['sw_num'] = symbol_sw_num_list
            '''
            该列数据含义:
                    添加机构分析申万三级行业中,在分析时间段内,被机分析过的上市公司数量和该申万行业中上市公司的总数,[3/8]表示,某个股票所有
                的申万三行业,共有8家上市公司,机构预测了其中3家上市公司的数据。从这个数据,可以发现某个时段中,机构对某个申万三级行业的关注情况。            
            '''

            # 添加增长率分析列
            def forecastgrowth(t, col1, col2):
                if np.isnan(t['forecast_num']): #20230627升级本段代码,将比例显示为百分数,便于阅读
                    return np.NaN
                else:
                    return str(100*round((t[col2] - t[col1]) / t[col1], 4))+'%'

            result_df['comp_t1'] = result_df.apply(forecastgrowth, col1='last_t', col2='this_t', axis=1)
            result_df['comp_t2'] = result_df.apply(forecastgrowth, col1='this_t', col2='correct_op_rt', axis=1)
            result_df['comp_n1'] = result_df.apply(forecastgrowth, col1='last_n', col2='this_n', axis=1)
            result_df['comp_n2'] = result_df.apply(forecastgrowth, col1='this_n', col2='correct_np', axis=1)
            # 调整列顺序
            final_data = result_df[
                ['ts_code', 'forecast_num', 'sw_num', 'last_t', 'last_n', 'this_t', 'this_n', 'op_rt', 'np',
                 'correct_op_rt', 'correct_np', 'comp_t1', 'comp_t2', 'comp_n1', 'comp_n2', 'fore_t', 'fore_n']]

            # 导出数据
            Basics.PandasSaveData(data=final_data, path_suffix=path2)
            '''
            ts_code             股票代码
            forecast_num        在选定时间段内该股票被机构预测的次数
            sw_num              在选定时间段内,申万三级行业内被机构预测的股票数量/该行业的所有股票的数量
            last_t              上一个报告期的总营业收入
            last_n              上一个报告期的归母净利润
            this_t              最近一个报告期的总营业收入
            this_n              最近一个报告期的归母净利润
            op_rt               预测的总营业收入(元)
            np                  预测的归母净利润(元)
            correct_op_rt       修正后的预测的总营业收入(元)
            correct_np          修正后的预测的归母净利润(元)
            fore_t              预测期的总营业收入(元),如果预测期大于当前日期,则该数据为nan,导出时为空值。预测日期为已经发生的日期,用于检查机构的预测结果时才用。
            fore_n              预测期的归母净利润(元),如果预测期大于当前日期,则该数据为nan,导出时为空值。预测日期为已经发生的日期,用于检查机构的预测结果时才用。
            comp_t1             最近一期总营收相比上一期总营收的增长率
            comp_t2             预测期总营收相比最近一期总营收的增长率
            comp_n1             最近一期归母净利润相比上一期归母净利润的增长率
            comp_n2             预测期归母净利润相比最近一期归母净利润的增长率
            '''
            # 函数返回值
            return final_data



class Technology():
    @classmethod
    def Hist_List(cls,HistMaxNum, HistMinNum, GroupNUm):  # 直方图列表函数 TODO 已归于类函数,由函数Hist_List升级而来
        '''

        :param HistMaxNum: 必须为正数,且必须大于HistMinNum
        :param HistMinNum: 必须为正数
        :param GroupNUm: 初步设置的直方图组数,实际生成的直方图组数,可能有点差异
        :return: 返回值是一个列表,用于描述直方图的组距或直方图X轴标记
        '''
        '''
        步骤:
            第1步:确定数据中的最大值、最小值以及二者差值       
            第2步:计算组数。组距和组数的确定没有固定标准
                  分组数的两种确定方法
                      A、中国质量协会注册质量经理手册中,推荐使用n的平方根。本例中,K= 60的平方根=7.7≈ 8。
                      B、日常中也常用斯特奇斯(Sturges)提出的经验公式K=1+lgn/lg2。按该公式,本例K=6.9≈7。
            第3步:确定组距。分为等组距和不等组距两种
            第4步:确定各组界限列表。
        组界范围:来源七年级下册数学教材
            149≤x<152
            152≤x<155
            155≤x<158
            161≤x<164
            167≤x<170
            170≤x<173      
        '''

        def RoundUp(num, ndigits=2):  # 向上四舍五入函数
            '''
            :param num: 传入需要处理的数据
            :param ndigits: 小数后的位数
            :return: 返回向上取整的结果
            '''
            if round(num, ndigits) >= num:
                return round(num, ndigits)
            else:
                return round(num, ndigits) + 1 / 10 ** ndigits

        def RoundDown(num, ndigits=2):  # 向下四舍五入函数
            '''
            :param num: 传入需要处理的数据
            :param ndigits: 小数后的位数
            :return: 返回向下取整的结果
            '''
            if round(num, ndigits) <= num:
                return round(num, ndigits)
            else:
                return round(num, ndigits) - 1 / 10 ** ndigits

        if HistMaxNum - HistMinNum >= 0 and HistMaxNum - HistMinNum < 0.1 * GroupNUm:
            ndigits = 2
            Adjust_Max = RoundUp(HistMaxNum, ndigits)  # 对直方图原始数据中的最大值向上进行进行四舍五入,功能同excel中的roundup函数
            Adjust_Bottom = RoundDown(HistMinNum, ndigits)  # 对直方图原始数据中的最大值向下进行进行四舍五入,功能同excel中的rounddown函数
            Width = round(RoundUp((Adjust_Max - Adjust_Bottom) / GroupNUm, ndigits), ndigits)  # 计算走方图的组距
            Original_Top_Num = round(Adjust_Bottom + GroupNUm * Width,
                                     ndigits)  # 初步计算直方图数据的上限值,Adjust_Bottom + 22 * width,该数据往往超出直方图数据范围
            Difference = round(Original_Top_Num - Adjust_Max, ndigits)  # Original_Top_Num和Adjust_Max二者相减的差
            Range_Max_Num = round(Original_Top_Num - int(Difference / Width) * Width, ndigits)  # 直方图组距离列表的上限值
            Hist_Num = int(1 + round((Range_Max_Num - Adjust_Bottom) / Width, 0))  # 直方图的组数
            Hist_List = [round(Adjust_Bottom + Width * i, ndigits) for i in range(Hist_Num)]  # 直方图的组距列表
            return Hist_List
        elif HistMaxNum - HistMinNum >= 0.1 * GroupNUm and HistMaxNum - HistMinNum < GroupNUm:
            ndigits = 1
            Adjust_Max = RoundUp(HistMaxNum, ndigits)  # 对直方图原始数据中的最大值向上进行进行四舍五入,功能同excel中的roundup函数
            Adjust_Bottom = RoundDown(HistMinNum, ndigits)  # 对直方图原始数据中的最大值向下进行进行四舍五入,功能同excel中的rounddown函数
            Width = round(RoundUp((Adjust_Max - Adjust_Bottom) / GroupNUm, ndigits), ndigits)  # 计算走方图的组距
            Original_Top_Num = round(Adjust_Bottom + GroupNUm * Width,
                                     ndigits)  # 初步计算直方图数据的上限值,Adjust_Bottom + 22 * width,该数据往往超出直方图数据范围
            Difference = round(Original_Top_Num - Adjust_Max, ndigits)  # Original_Top_Num和Adjust_Max二者相减的差
            Range_Max_Num = round(Original_Top_Num - int(Difference / Width) * Width, ndigits)  # 直方图组距离列表的上限值
            Hist_Num = int(1 + round((Range_Max_Num - Adjust_Bottom) / Width, 0))  # 直方图的组数
            Hist_List = [round(Adjust_Bottom + Width * i, ndigits) for i in range(Hist_Num)]  # 直方图的组距列表
            return Hist_List
        elif HistMaxNum - HistMinNum >= GroupNUm and HistMaxNum - HistMinNum < 10 * GroupNUm:
            ndigits = 0
            Adjust_Max = RoundUp(HistMaxNum, ndigits)  # 对直方图原始数据中的最大值向上进行进行四舍五入,功能同excel中的roundup函数
            Adjust_Bottom = RoundDown(HistMinNum, ndigits)  # 对直方图原始数据中的最大值向下进行进行四舍五入,功能同excel中的rounddown函数
            Width = round(RoundUp((Adjust_Max - Adjust_Bottom) / GroupNUm, ndigits), ndigits)  # 计算走方图的组距
            Original_Top_Num = round(Adjust_Bottom + GroupNUm * Width,
                                     ndigits)  # 初步计算直方图数据的上限值,Adjust_Bottom + 22 * width,该数据往往超出直方图数据范围
            Difference = round(Original_Top_Num - Adjust_Max, ndigits)  # Original_Top_Num和Adjust_Max二者相减的差
            Range_Max_Num = round(Original_Top_Num - int(Difference / Width) * Width, ndigits)  # 直方图组距离列表的上限值
            Hist_Num = int(1 + round((Range_Max_Num - Adjust_Bottom) / Width, 0))  # 直方图的组数
            Hist_List = [round(Adjust_Bottom + Width * i, ndigits) for i in range(Hist_Num)]  # 直方图的组距列表
            return Hist_List
        elif HistMaxNum - HistMinNum >= 10 * GroupNUm and HistMaxNum - HistMinNum < 100 * GroupNUm:
            ndigits = -1
            Adjust_Max = RoundUp(HistMaxNum, ndigits)  # 对直方图原始数据中的最大值向上进行进行四舍五入,功能同excel中的roundup函数
            Adjust_Bottom = RoundDown(HistMinNum, ndigits)  # 对直方图原始数据中的最大值向下进行进行四舍五入,功能同excel中的rounddown函数
            Width = round(RoundUp((Adjust_Max - Adjust_Bottom) / GroupNUm, ndigits), ndigits)  # 计算走方图的组距
            Original_Top_Num = round(Adjust_Bottom + GroupNUm * Width,
                                     ndigits)  # 初步计算直方图数据的上限值,Adjust_Bottom + 22 * width,该数据往往超出直方图数据范围
            Difference = round(Original_Top_Num - Adjust_Max, ndigits)  # Original_Top_Num和Adjust_Max二者相减的差
            Range_Max_Num = round(Original_Top_Num - int(Difference / Width) * Width, ndigits)  # 直方图组距离列表的上限值
            Hist_Num = int(1 + round((Range_Max_Num - Adjust_Bottom) / Width, 0))  # 直方图的组数
            Hist_List = [round(Adjust_Bottom + Width * i, ndigits) for i in range(Hist_Num)]  # 直方图的组距列表
            return Hist_List
        elif HistMaxNum - HistMinNum >= 100 * GroupNUm:
            ndigits = -1
            Adjust_Max = RoundUp(HistMaxNum, ndigits)  # 对直方图原始数据中的最大值向上进行进行四舍五入,功能同excel中的roundup函数
            Adjust_Bottom = RoundDown(HistMinNum, ndigits)  # 对直方图原始数据中的最大值向下进行进行四舍五入,功能同excel中的rounddown函数
            Width = round(RoundUp((Adjust_Max - Adjust_Bottom) / GroupNUm, ndigits), ndigits)  # 计算走方图的组距
            Original_Top_Num = round(Adjust_Bottom + GroupNUm * Width,
                                     ndigits)  # 初步计算直方图数据的上限值,Adjust_Bottom + 22 * width,该数据往往超出直方图数据范围
            Difference = round(Original_Top_Num - Adjust_Max, ndigits)  # Original_Top_Num和Adjust_Max二者相减的差
            Range_Max_Num = round(Original_Top_Num - int(Difference / Width) * Width, ndigits)  # 直方图组距离列表的上限值
            Hist_Num = int(1 + round((Range_Max_Num - Adjust_Bottom) / Width, 0))  # 直方图的组数
            Hist_List = [round(Adjust_Bottom + Width * i, ndigits) for i in range(Hist_Num)]  # 直方图的组距列表
            return Hist_List

    @classmethod
    def Day_Index_Hist(cls,symbol, target, GroupNUm):  # TODO 已转化为类函数,由函数Day_Index_Hist升级而来
        '''
        :param symbol: 股票代码,数据类型:字符串
        :param target: 指标代码
            该参数的值:
                pe  市盈率(总市值/净利润, 亏损的PE为空)
                pb  市净率(总市值/净资产)
                ps  市销率
                turnover_rate   换手率
                volume_ratio    量比
        :param GroupNUm: 初步设置的直方图组数,实际生成的直方图组数,可能有点差异
        :return: 生成对应指标的直方图
        '''
        # 1.股票名称数据
        symbol_data = pro.stock_basic(exchange='', list_status='L', fields='ts_code,symbol,name')
        symbol_data.index = symbol_data.loc[:, 'symbol']  # 修改列索引
        name_data = symbol_data.drop(labels='symbol', axis=1)  # 删除t1中不需要的第0列
        # 2.指标数据
        ts_code = name_data.loc[symbol, 'ts_code']
        index_data = pro.daily_basic(ts_code=ts_code, fields='{}'.format(target))
        # 3.创建原始数据列表和组距列表
        Hist_Data = index_data.loc[:, target].values  # 直方图数据
        Ticks_List = Technology.Hist_List(max(Hist_Data), min(Hist_Data), GroupNUm)
        # 设置图形尺寸文字
        plt.figure(figsize=(20, 8), dpi=80)
        plt.rcParams['font.sans-serif'] = 'Microsoft YaHei'
        # # 绘制直方图
        plt.hist(Hist_Data, Ticks_List)
        # # 设置刻度
        plt.xticks(Ticks_List)
        # 设置图表名称
        plt.title('{}直方图({})'.format(symbol, target), fontsize=28)
        # 设置网格
        plt.grid()
        plt.show()
    @classmethod
    def MA_Func(cls,ts_code, MA_Num):  # 生成制作移动平均线数据的函数
        '''
        :param symbol: 股票代码,或同花顺行业,概念,区域及特色指数,数据类型:字符串
        :param MA_Num: 移动平均线的日期数,比如MA5,MA10,MA20,而MA_Num就是表示这里的5,10,20
        :return: 该返回值依次为数据显示的X轴数据范围,MA数据,X刻度文本对应的X轴刻度值,X轴刻度列表,股票名称
        '''

        # 获取同花顺行业列表或股票列表数据
        global data20230618
        if ts_code[-2:] == 'TI':
            data20230618 = pd.DataFrame()
            type_list = ['N', 'I', 'R', 'S']
            for i in type_list:
                df_industry = pro.ths_index(exchange='A', type=i)
                data20230618 = pd.concat([data20230618, df_industry])
                time.sleep(1.5)

        else:
            data20230618 = pro.stock_basic(exchange='', list_status='L', fields='ts_code,symbol,name')
        data20230618.index = [i for i in data20230618['ts_code'].values]

        # 1.获取股票原始数据
        global df20230618
        if ts_code[-2:] == 'TI':
            df20230618 = pro.ths_daily(ts_code=ts_code, fields='ts_code,trade_date,open,close,high,low,pct_change')
        else:
            DayClosePrice = pro.daily(ts_code=ts_code)  # 自上市至今的收盘价数据
            list1 = DayClosePrice.loc[:, 'trade_date'].values
            OneYearDateList = [Today_Date_Count(-365 + i) for i in range(1, 366)]
            if list1[-1] in OneYearDateList:
                df20230618 = pro.daily(ts_code=ts_code)  # 自上市至今天的收盘价
            else:
                df20230618 = pro.daily(ts_code=ts_code, start_date=Today_Date_Count(-365),
                                       end_date=Today_Date_Count())  # 一年内的收盘价
        # 2.构造移动平均价列表
        Data_Close_List = df20230618.loc[:, 'close'].values  # 由收盘价构成的列表
        length = len(Data_Close_List)  # 收盘价的个数
        Opposite_Sort_List = [Data_Close_List[length - 1 - i] for i in
                              range(length)]  # 将原始数据Data_Close_List中的数据按相反的顺序排列,生成新列表
        MovingAveragesList = []  # 由移动平均数据构成的列表
        for i in range(0, length - MA_Num + 1):
            MovingAverages = round(sum(Opposite_Sort_List[i:i + MA_Num]) / MA_Num, 2)
            MovingAveragesList.append(MovingAverages)
        # 3.数据显示范围
        XRangeList = [i for i in range(MA_Num, length + 1)]
        # 4.日期列表
        Data_Date_List = df20230618.loc[:, 'trade_date'].values  # 原始数据构成的日期列表
        Opposite_Date_List = [Data_Date_List[length - 1 - i] for i in range(length)]
        int_num = int((length) / 30)  # 需要显示移动平均线的日期个数
        # 5.X轴标签列表
        xNum = [length - (int_num) * 30 + 30 * i for i in range(int_num + 1)]  # 显示标记对应位置的x轴刻度列表
        Xticks_List = [Opposite_Date_List[length - (int_num) * 30 + 30 * i - 1] for i in range(int_num + 1)]

        # 6.股票名称
        name = data20230618.loc[ts_code, 'name']
        # 7.函数返回值
        result = (
            XRangeList, MovingAveragesList, xNum, Xticks_List,
            name)  # 该返回值依次为数据显示的X轴数据范围,MA数据,X刻度文本对应的X轴刻度值,X轴刻度列表,股票名称
        return result

    @classmethod
    def MA_Figure(cls, ts_code, max_num, center_num, min_num):  # 绘制移动平均线的函数 TODO 已转化为类函数
        '''
        :param max_num: 最大的移动平均线天数
        :param center_num: 居中的移动平均线天数
        :param min_num: 最小的移动平均线天数
        :return: 显示三线移动平均线图形
        '''
        # 1.获取移动平均线数据
        a_max, b_max, c_max, d_max, e_max = Technology.MA_Func(ts_code,max_num)
        a_center, b_center, c_center, d_center, e_center = Technology.MA_Func(ts_code,center_num)
        a_min, b_min, c_min, d_min, e_min = Technology.MA_Func(ts_code,min_num)
        # 2.绘图
        plt.figure(figsize=(20, 2.5), dpi=80)  # 设置尺寸和分辨率
        plt.rcParams['font.sans-serif'] = 'Microsoft YaHei'  # 设置字体
        if len(b_max) == 0:
            plt.plot(0, 0, label="MA{}:空值".format(max_num), color='r')
        else:
            plt.plot(a_max, b_max, label="MA{}:{}".format(max_num, b_max[len(b_max) - 1]), color='r')

        if len(b_center) == 0:
            plt.plot(0, 0, label="MA{}:空值".format(center_num), color='b')
        else:
            plt.plot(a_center, b_center, label="MA{}:{}".format(center_num, b_center[len(b_center) - 1]), color='b')
        if len(b_min) == 0:
            plt.plot(0, 0, label="MA{}:空值".format(min_num), color='y')
        else:
            plt.plot(a_min, b_min, label="MA{}:{}".format(min_num, b_min[len(b_min) - 1]), color='y')
        plt.xticks(c_max, d_max, rotation=0)  # 设置刻度
        # plt.subplots_adjust(left=0.1,bottom=0.25) # 设置图形的边距
        plt.grid()  # 设置网格线
        plt.legend(loc=2)  # 显示图例位置
        plt.title('一年内{}({})移动平均线MA变化规律'.format(e_max, ts_code), fontsize=12)  # 设置图表名称
        plt.show()
    @classmethod
    def MACD_Func(cls,ts_code, dayNum=550):  # 股票MACD数据 TODO 已转化为类函数,由函数MACD_Data_Func()升级而来
        '''

        :param symbol: 股票代码,数据类型:字符串
        :param dayNum: 如果股票上市时间太久,选取收盘价数据的起始日期距离今天的日期天数。该参数默认是550天,即约一年半。
                        因为试验结果中贵州茅台的最长无影响天数为265天。选取该参数,保证一只股MACD指标距离当前日期最少
                        的MACD正确指标天数有285天以上,以供分析判断该指标用。
        :return:返回值是一个元组,内含以下数据:MACD_LIST,DIF_LIST,DEA_LIST,XRangeList,xNum,Xticks_List,name
        '''

        # 获取同花顺行业列表或股票列表数据
        global data20230618
        if ts_code[-2:] == 'TI':
            data20230618 = pd.DataFrame()
            type_list = ['N', 'I', 'R', 'S']
            for i in type_list:
                df_industry = pro.ths_index(exchange='A', type=i)
                data20230618 = pd.concat([data20230618, df_industry])
                time.sleep(1.5)
        else:
            data20230618 = pro.stock_basic(exchange='', list_status='L', fields='ts_code,symbol,name')
        data20230618.index = [i for i in data20230618['ts_code'].values]

        # 1.获取股票原始数据

        global df20230619
        if ts_code[-2:] == 'TI':
            df20230619 = pro.ths_daily(ts_code=ts_code, fields='ts_code,trade_date,open,close,high,low,pct_change')
        else:
            DayClosePrice = pro.daily(ts_code=ts_code)  # 自上市至今的收盘价数据
            list1 = DayClosePrice.loc[:, 'trade_date'].values
            OneYearDateList = [Today_Date_Count(-dayNum + i) for i in range(1, dayNum + 1)]
            if list1[-1] in OneYearDateList:
                df20230619 = pro.daily(ts_code=ts_code)  # 自上市至今天的收盘价
            else:
                df20230619 = pro.daily(ts_code=ts_code, start_date=Today_Date_Count(-dayNum),
                                       end_date=Today_Date_Count())  # 一年内的收盘价
            '''
            经试验得出结论:选取自今日向前选取N个日历日,作为收盘价起始数据的日期,这个日期至今天的日历天数,
            定义为MACD指标无影响天数(自己定义的概念)。下面是试验得出的结果。其结论:整体上看,股价越低,无影
            响天数越少。        
                贵州茅台600519  无影响天数265天
                上海机场600009  无影响天数160天
                宇通客车600066  无影响天数65天
                中国银行601988  无影响天数85天
                比亚迪002594   无影响天数200天
                五粮液000858   无影响天数180天
            '''
        # 2.构造收盘价日期从前到后的数据列表
        list1 = df20230619.loc[:, 'close'].values  # 由收盘价构成的列表
        length = len(list1)  # 原始数据列表的数据个数
        Opposite_Sort_List = [list1[length - 1 - i] for i in
                              range(length)]  # 将原始数据list中的数据按相反的顺序排列,生成新列表

        EMA12_LIST = []  # EMA12列表
        EMA26_LIST = []  # EMA26列表
        DIF_LIST = []  # DIF列表
        DEA_LIST = []  # DEA列表
        MACD_LIST = []  # MACD列表
        for i in range(length):  # 创建 MACD数据的循环
            if i == 0:
                # 创建EMA12列表
                EMA12 = Opposite_Sort_List[0]
                EMA12_LIST.append(EMA12)
                # 创建EMA26列表
                EMA26 = Opposite_Sort_List[0]
                EMA26_LIST.append(EMA26)
                # 创建DIF列表
                DIF = round(EMA12 - EMA26, 2)
                DIF_LIST.append(DIF)
                # 创建DEA列表
                DEA = 0
                DEA_LIST.append(DEA)
                # 创建MACD列表
                MACD = 2 * (DIF - DEA)
                MACD_LIST.append(MACD)
            elif i > 0:
                # 创建EMA12列表
                EMA12 = (2 * Opposite_Sort_List[i] + 11 * EMA12_LIST[i - 1]) / 13
                EMA12_LIST.append(EMA12)
                # 创建EMA26列表
                EMA26 = (2 * Opposite_Sort_List[i] + 25 * EMA26_LIST[i - 1]) / 27
                EMA26_LIST.append(EMA26)
                # 创建DIF列表
                DIF = round(EMA12 - EMA26, 2)
                DIF_LIST.append(DIF)
                # 创建DEA列表
                DEA = round(0.2 * (EMA12 - EMA26) + 0.8 * DEA_LIST[i - 1], 2)
                DEA_LIST.append(DEA)
                # 创建MACD列表
                MACD = round(2 * ((EMA12 - EMA26) - (0.2 * (EMA12 - EMA26) + 0.8 * DEA_LIST[i - 1])),
                             2)  # 为保证精度,不使用中间计算2*(DIF-DEA)计算
                MACD_LIST.append(MACD)

        # 4.数据显示范围
        XRangeList = [i for i in range(1, length + 1)]
        # 5.日期列表
        Data_Date_List = df20230619.loc[:, 'trade_date'].values  # 原始数据构成的日期列表
        Opposite_Date_List = [Data_Date_List[length - 1 - i] for i in range(length)]
        int_num = int((length) / 30)  # 需要显示移动平均线的日期个数
        # 6.X轴标签列表
        xNum = [length - (int_num) * 30 + 30 * i for i in range(int_num + 1)]  # 显示标记对应位置的x轴刻度列表
        Xticks_List = [Opposite_Date_List[length - (int_num) * 30 + 30 * i - 1] for i in range(int_num + 1)]
        # 7.股票名称
        name = data20230618.loc[ts_code, 'name']
        # 8.交易日期列表
        trade_date_list = [i for i in df20230619.loc[:, 'trade_date'].values]
        Length = len(trade_date_list)  # 日期从后向前排序的交易日期列表

        Trade_Date_List = [trade_date_list[Length - 1 - i] for i in
                           range(Length)]  # 日期从前向后排序的交易日期列表

        # 8.返回值
        result = (MACD_LIST, DIF_LIST, DEA_LIST, XRangeList, xNum, Xticks_List, name, Trade_Date_List)
        return result

    @classmethod
    def MACD_Figure(cls,ts_code, dayNum=550):  # 绘制移动平均线的函数 TODO 已转化为类函数,由函数MACD_Figure升级而来
        '''

        :param symbol: 股票代码,数据类型:字符串
        :param dayNum: 该参数的说明同MACD_Data_Func函数
        :return: 返回MACD图表
        '''

        # 1.获取移动平均线数据
        # a, b, c, d, e, f, g, h = MACD_Data_Func(symbol, dayNum=dayNum)
        a, b, c, d, e, f, g, h = Technology.MACD_Func(ts_code=ts_code,dayNum=dayNum)
        # 2.绘图
        plt.figure(figsize=(20, 2.5), dpi=80)  # 设置尺寸和分辨率
        plt.rcParams['font.sans-serif'] = 'Microsoft YaHei'  # 设置字体
        plt.plot(0, 0, label='MACD:{}'.format(a[len(a) - 1]), color='r')  # 实际无图形,绘制一个点,用于显示MACD的图例及数值
        plt.plot(d, b, label='DIF:{}'.format(b[len(b) - 1]), color='y')
        plt.plot(d, c, label='DEA:{}'.format(c[len(c) - 1]), color='b')
        # 设置刻度
        plt.xticks(e, f)

        # plt.subplots_adjust(left=0.1,bottom=0.25) # 设置图形的边距
        plt.grid()  # 设置网格线
        plt.legend(loc=2)  # 显示图例位置
        plt.title('550天内{}({})平滑异动移动平均线MACAD变化规律'.format(g, ts_code), fontsize=12)  # 设置图表名称
        plt.show()
    @classmethod
    def MACD_Judge(cls,ts_code, RecentTradeDayNum=1):  # TODO 已转化为类函数,由函数Judge_MACD升级而来
        '''

        :param symbol: 股票代码,数据类型,字符串
        :param RecentTradeDayNum: 设置用于筛选MACD的最近交易日数,若执行本函数在下午17:00前,则不包括当前交易日
        :return: 返回一个元组,用于判断指定日期是否出现四种交叉点,若是则返回字符Y,否则返回字符N。从前至后分别表示零上金叉,零下金叉,零上死叉,零下死叉的判断结果
        '''
        # MACD_LIST, DIF_LIST, DEA_LIST, _, _, _, _, Trade_Date_List = MACD_Data_Func(symbol)
        MACD_LIST, DIF_LIST, DEA_LIST, _, _, _, _, Trade_Date_List = Technology.MACD_Func(ts_code=ts_code)
        LEN = len(MACD_LIST)
        # 四种交叉点日期列表
        UpGlodCrossingDate_1 = []  # 零上金叉日期列表
        DownGlodCrossingDate_2 = []  # 零下金叉日期列表
        UpDieCrossingDate_3 = []  # 零上死叉日期列表
        DownDieCrossingDate_4 = []  # 零下死叉日期列表

        # print('1.零上金叉'.center(60, '-'))
        for i in range(LEN):
            if (i > 0 and i < LEN - 1) and (DIF_LIST[i] > 0 and MACD_LIST[i] > 0) and (
                    MACD_LIST[i - 1] < 0 and MACD_LIST[i] > 0):  # 上穿
                # print(Trade_Date_List[i], '\t', '零上金叉')
                UpGlodCrossingDate_1.append(Trade_Date_List[i])
        # print('2.零下金叉'.center(60, '-'))
        for i in range(LEN):
            if (i > 0 and i < LEN - 1) and (DIF_LIST[i] < 0 and MACD_LIST[i] > 0) and (
                    MACD_LIST[i - 1] < 0 and MACD_LIST[i] > 0):  # 上穿
                # print(Trade_Date_List[i], '\t', '零下金叉')
                DownGlodCrossingDate_2.append(Trade_Date_List[i])
        # print('3.零上死叉'.center(60, '-'))
        for i in range(LEN):
            if (i > 0 and i < LEN - 1) and (DIF_LIST[i] > 0 and MACD_LIST[i] < 0) and (
                    MACD_LIST[i - 1] > 0 and MACD_LIST[i] < 0):  # 下破
                # print(Trade_Date_List[i], '\t', '零上死叉')
                UpDieCrossingDate_3.append(Trade_Date_List[i])
        # print('4.零下死叉'.center(60, '-'))
        for i in range(LEN):
            if (i > 0 and i < LEN - 1) and (DIF_LIST[i] < 0 and MACD_LIST[i] < 0) and (
                    MACD_LIST[i - 1] > 0 and MACD_LIST[i] < 0):
                # print(Trade_Date_List[i], '\t', '零下死叉')
                DownDieCrossingDate_4.append(Trade_Date_List[i])

        # RecentTradeDayNum = 1  # 最近交易日数,若执行本函数在下午17:00前,则不包括当前交易日
        List1 = Trade_Date_List[-RecentTradeDayNum:-1] + [Trade_Date_List[-1]]
        # print(List1)

        # 最近指定日期内四种交叉点存在的日期列表
        JudgeList1 = []  # 零上金叉最近指定日期列表
        for i in UpGlodCrossingDate_1:
            if i in List1:
                # print('是')
                JudgeList1.append(i)
        JudgeList2 = []  # 零下金叉最近指定日期列表
        for i in DownGlodCrossingDate_2:
            if i in List1:
                # print('是')
                JudgeList2.append(i)
        JudgeList3 = []  # 零上死叉最近指定日期列表
        for i in UpDieCrossingDate_3:
            if i in List1:
                # print('是')
                JudgeList3.append(i)
        JudgeList4 = []  # 零下死叉最近指定日期列表
        for i in DownDieCrossingDate_4:
            if i in List1:
                # print('是')
                JudgeList4.append(i)

        # 判断指定日期是否出现零上金叉,若是则返回字符Y,否则返回字符N,其他三个交叉点判断方法相同
        result1 = 0
        if len(JudgeList1) > 0:
            result1 = 'Y'
        elif len(JudgeList1) == 0:
            result1 = 'N'
        # 判断指定日期是否出现零下金叉
        result2 = 0
        if len(JudgeList2) > 0:
            result2 = 'Y'
        elif len(JudgeList2) == 0:
            result2 = 'N'
        # 判断指定日期是否出现零下金叉
        result3 = 0
        if len(JudgeList3) > 0:
            result3 = 'Y'
        elif len(JudgeList3) == 0:
            result3 = 'N'
        # 判断指定日期是否出现零下金叉
        result4 = 0
        if len(JudgeList4) > 0:
            result4 = 'Y'
        elif len(JudgeList4) == 0:
            result4 = 'N'

        Tuple = (result1, result2, result3, result4)

        return Tuple
    @classmethod
    def MACD_Shape(cls,RecentTradeDayNum, judge_type):  # MACD四叉分析 TODO 已转化为类函数,由函数MACD_FourCrossingAnalysis()升级而来
        '''
        :param RecentTradeDayNum:   最近交易日数,若执行本函数在下午17:00前,则不包括当前交易日
        :return: 生成一份MACD的死叉分析表,格式为csv格式数据,保存路径为:'./LoadFile/TechnologicalAnalysis/MACD/MACD四叉分析.csv'
        '''
        now1 = datetime.datetime.now()
        global mydata20230618, name_prefix20230618

        if judge_type == 'stock':
            mydata20230618 = pro.stock_basic(exchange='', list_status='L', fields='ts_code,symbol,name')
            name_prefix20230618 = '股票'
        elif judge_type == 'N':
            mydata20230618 = pro.ths_index(exchange='A', type='N')
            name_prefix20230618 = '板块'
        elif judge_type == 'I':
            mydata20230618 = pro.ths_index(exchange='A', type='I')
            name_prefix20230618 = '行业'
        elif judge_type == 'R':
            mydata20230618 = pro.ths_index(exchange='A', type='R')
            name_prefix20230618 = '地域'
        elif judge_type == 'S':
            mydata20230618 = pro.ths_index(exchange='A', type='S')
            name_prefix20230618 = '特色'

        ts_code_list = [i for i in mydata20230618['ts_code'].values]
        mydata20230618.index = ts_code_list

        # 判断文件是否为最新文件
        save_path20230619 = f'/Technology/MACD_Shape/{name_prefix20230618}MACD形态分析.csv'
        bool_value,final_save_path_str=Basics.IsNewDataFile(path_suffix=save_path20230619)
        if bool_value: # 增加一个逻辑判断,如果是最新文件,则不需要重新生成文件
            print('已是最新文件,无需更新!')
        else:
            # 使用循环获取数据
            DataFrameList = []  # 用于接收单只股票MACD分析结果dict1的列表,这个列表用于创建DataFrame
            global my_save_data20230619
            for i in ts_code_list:
                try:
                    a, b, c, d = Technology.MACD_Judge(ts_code=i, RecentTradeDayNum=RecentTradeDayNum)
                    dict1 = {'代码': i, '{}名称'.format(name_prefix20230618): mydata20230618.loc[i, 'name'], '零上金叉': a,
                             '零下金叉': b, '零上死叉': c, '零下死叉': d}
                    print(dict1)
                    DataFrameList.append(dict1)
                    my_save_data20230619 = pd.DataFrame(DataFrameList)
                except:
                    a, b, c, d = np.NaN, np.NaN, np.NaN, np.NaN
                    dict1 = {'代码': i, '{}名称'.format(name_prefix20230618): mydata20230618.loc[i, 'name'], '零上金叉': a,
                             '零下金叉': b, '零上死叉': c, '零下死叉': d}
                    print(dict1)
                    DataFrameList.append(dict1)
                    my_save_data20230619 = pd.DataFrame(DataFrameList)

            def Shape(t):
                col_list = ['零上金叉', '零下金叉', '零上死叉', '零下死叉']
                return_list = []
                for i in col_list:
                    if t[i] == 'Y':
                        return_list.append(i)
                return ','.join(return_list)

            my_save_data20230619['MACD形状'] = my_save_data20230619.apply(Shape, axis=1)

            Basics.PandasSaveData(data=my_save_data20230619, path_suffix=save_path20230619)

        # 4.分析执行完本函数所需要的时间
        now2 = datetime.datetime.now()
        print(now2 - now1)
    @classmethod
    def MA_Judge(cls,min_num, middle_num, max_num, judge_num, judge_type='stock'):  # TODO 已转化为类函数,由函数Judge_MA()升级而来
        '''
        本函数运行时间较长,其中同花顺三个行业运行时间大概为5个小时,股票运行时间大概6个小时,应采用线程或进程运行本函数
        :param min_num: 三条MA线中的MA的最小天数
        :param middle_num: 三条MA线中的MA的中间天数
        :param max_num: 三条MA线中的MA的最大天数
        :param judge_num:用于判断MA多头排列,空头排列,多头发柜,空头发散的天数,比如设置judge_num=5,
                           同时前面三个参数分别设置为5,10,20,则表示在多头排列判断中,5天内MA5,MA10,
                           MA20这个数值中,均为MA5最大
        :param judge_type: 表示判断行情的类型,其参数为以下值:
                            stock   表示股票的MA
                            N       表示同花顺板块
                            I       表示同花顺行业
                            R       表示同花区域
                            S       表示同花顺特色数据,但目前本程序暂时不支持,运行时会报错,原因待查
        :return:
        '''

        start_time = datetime.datetime.now()

        # 1.获取股票名称列表或同花顺行业列表

        global mydata20230618, name_prefix20230618

        if judge_type == 'stock':
            mydata20230618 = pro.stock_basic(exchange='', list_status='L', fields='ts_code,symbol,name')
            name_prefix20230618 = '股票'
        elif judge_type == 'N':
            mydata20230618 = pro.ths_index(exchange='A', type='N')
            name_prefix20230618 = '板块'
        elif judge_type == 'I':
            mydata20230618 = pro.ths_index(exchange='A', type='I')
            name_prefix20230618 = '行业'
        elif judge_type == 'R':
            mydata20230618 = pro.ths_index(exchange='A', type='R')
            name_prefix20230618 = '地域'
        elif judge_type == 'S':
            mydata20230618 = pro.ths_index(exchange='A', type='S')
            name_prefix20230618 = '特色'

        ts_code_list = [i for i in mydata20230618['ts_code'].values]
        mydata20230618.index = ts_code_list

        # 判断文件是否为最新文件

        my_save_path = f'/Technology/MA_Judge/{name_prefix20230618}MA分析结果.csv'

        bool_value, final_save_path_str = Basics.IsNewDataFile(path_suffix=my_save_path)

        if bool_value: # 增加一个逻辑判断,如果是最新文件,则不需要重新生成文件
            print('已是最新文件,无需更新!')
        else:
            # 使用循环进行判断
            MALIST = []  # 该列表用于接收每只股票的MA分析结果,单个元素为字典类型,字典中所包含数据,循环中的代码
            global my_result_data20230619
            for i in ts_code_list:
                try:
                    _, a, _, _, _ = Technology.MA_Func(ts_code=i, MA_Num=min_num)  # 获取MA列表
                    _, b, _, _, _ = Technology.MA_Func(ts_code=i, MA_Num=middle_num)  # 获取MA列表
                    _, c, _, _, _ = Technology.MA_Func(ts_code=i, MA_Num=max_num)  # 获取MA列表

                    # 判断多头排列
                    list_d = []  # 天数judge_num内的布尔值列表
                    d = None  # 用于接收多头判断结果
                    for j in range(judge_num):
                        b1 = a[-1 - j] > b[-1 - j] and b[-1 - j] > c[-1 - j] and a[-1-j]>a[-1-j-1] # TODO
                        list_d.append(b1)
                    if False not in list_d:
                        d = 'Y'
                    else:
                        d = 'N'

                    # 判断空头排列
                    list_k = []  # 天数judge_num内的布尔值列表
                    k = None  # 用于接收空头判断结果
                    for j in range(judge_num):
                        b1 = a[-1 - j] < b[-1 - j] and b[-1 - j] < c[-1 - j] and a[-1-j]<a[-1-j-1] # TODO
                        list_k.append(b1)

                    if False not in list_k:
                        k = 'Y'
                    else:
                        k = 'N'
                    # 判断多头发散
                    list_s = []
                    s = None  # 用于接收多头发散判断结果
                    for j in range(judge_num):
                        b1 = (a[-1 - j] - b[-1 - j]) > (a[(-1 - j) - 1] - b[(-1 - j) - 1]) and (b[-1 - j] - c[-1 - j]) > (
                                b[(-1 - j) - 1] - c[(-1 - j) - 1]) and a[-1-j]>a[-1-j-1] #TODO
                        list_s.append(b1)
                    if False not in list_s:
                        s = 'Y'
                    else:
                        s = 'N'
                    # 判断空头发散
                    list_kf = []
                    kf = None  # 用于接收空头发散判断结果
                    for j in range(judge_num):
                        b1 = (a[-1 - j] - b[-1 - j]) < (a[(-1 - j) - 1] - b[(-1 - j) - 1]) and (b[-1 - j] - c[-1 - j]) < (
                                b[(-1 - j) - 1] - c[(-1 - j) - 1]) and a[-1-j]<a[-1-j-1] # TODO
                        list_kf.append(b1)
                    if False not in list_kf:
                        kf = 'Y'
                    else:
                        kf = 'N'
                    # --------------------------以下为2023年6月20日新增代码--------------------------
                    # 均线粘合
                    p=0.002 # 该参数用于设置判断均线上下浮动的比例
                    m=a # 该参数用于设置判断使用哪条均线判断均线粘合
                    list_nh=[]
                    nh = None

                    for j in range(judge_num):
                        ll1=m[-1]
                        ll2=m[-1-j-1]
                        b1=abs(round((ll1-ll2)/ll2,5))<p
                        list_nh.append(b1)

                    if False not in list_nh:
                        nh = 'Y'
                    else:
                        nh = 'N'

                    # --------------------------以上为2023年6月20日新增代码--------------------------
                    dict1 = {'TS码': i, f'{name_prefix20230618}名称': mydata20230618.loc[i, 'name'], '多头排列': d,
                             '空头排列': k, '多头发散': s, '空头发散': kf,'均线粘合':nh}
                    print(dict1)
                    MALIST.append(dict1)
                    my_result_data20230619 = pd.DataFrame(MALIST)
                except:
                    dict1 = {'TS码': i, f'{name_prefix20230618}名称': mydata20230618.loc[i, 'name'], '多头排列': np.NaN,
                             '空头排列': np.NaN,
                             '多头发散': np.NaN, '空头发散': np.NaN,
                             '均线粘合':np.NaN}
                    print(dict1)
                    MALIST.append(dict1)
                    my_result_data20230619 = pd.DataFrame(MALIST)

            def Shape(t):
                col_list = ['多头排列', '空头排列', '多头发散', '空头发散','均线粘合']
                return_list = []
                for i in col_list:
                    if t[i] == 'Y':
                        return_list.append(i)
                return ','.join(return_list)

            # 添加形态列并导出数据

            my_result_data20230619['MA形态']=my_result_data20230619.apply(Shape,axis=1)

            Basics.PandasSaveData(data=my_result_data20230619, path_suffix=my_save_path)

        end_time = datetime.datetime.now()
        print(end_time-start_time)
    @classmethod
    def Volume(cls,ts_code, trade_date):
        '''
        :param ts_code: 股票代码
        :param trade_date: 交易日期,若为非交易日,则报错
        :return: 返回一个元组,四个元素含义如下:
                    volume          成交量
                    amount          成交金额
                    net_volume      净流入量
                    net_amount      净流入金额
        '''

        '''
        1.各类别统计规则如下:
            小单:5万以下 中单:5万~20万 大单:20万~100万 特大单:成交额>=100万
        2.关于净流入量和净流入金额,见资料:
            http://www.yjcf360.com/shichangpingce/878707.htm
        3.其他学习资料
            关于成交量的学习资料:
            vol资料:https://zhuanlan.zhihu.com/p/561807926
            成交量指标:https://www.cadforex.com/gupiao/63026.html
        '''
        '''
        量增价平    转阳信号
        量增价升    买入信号
        量减价平    变盘信号
        量减价跌    卖出信号
        量平价跌    持续卖出
        量增价跌    离场观望

        老陆:
            价在前,量在后。有价自然就有量,有量不一定有价。
            股价上涨,温和平稳放量是真实有效的。
            突然放巨量拉升股价,有可能是主力通过对倒来引发市场的关注。这个股份很可能就太虚了。接下来回调的可能性会加大。
            高位出现天量,同时在分时线上出现多次的诱多行为,那最好进行减仓,虽然天量并不代表这个行情整个结束,但是大多会伴随阶段性调整
            底部局量,一旦价格能够站稳在放巨量的那一天的价格,则离主升浪也不远了。
            股价在高位能否站稳,需要有持续的放量。高位温和的持续放量,意味着价格有效。
            高位缩量,意味着没有人来承接了,价格越高越危险,随时可能下跌,一定要回避。
            股份在低位是否持续下跌,我们需要观察是否持续的缩量。
            缩量下跌不是底啊,如果在高位下来的时候,这句话是对的。缩量证明上方的套牢盘以及恐慌盘没有完全被释放。他必需经过放量的这样一个阶段。
            但是整体的下跌周期末期缩量,则意味着跌无可跌。

        '''
        # 构造成交量函数的试验数据
        df = pro.moneyflow(ts_code=ts_code)
        df.index = df.loc[:, 'trade_date']
        # 计算成交量
        v1 = df.loc[trade_date, 'buy_sm_vol']
        v2 = df.loc[trade_date, 'buy_md_vol']
        v3 = df.loc[trade_date, 'buy_lg_vol']
        v4 = df.loc[trade_date, 'buy_elg_vol']
        volume = v1 + v2 + v3 + v4

        # 计算成交额
        a1 = df.loc[trade_date, 'buy_sm_amount']
        a2 = df.loc[trade_date, 'buy_md_amount']
        a3 = df.loc[trade_date, 'buy_lg_amount']
        a4 = df.loc[trade_date, 'buy_elg_amount']
        amount = a1 + a2 + a3 + a4

        # 计算净流入量
        net_volume = df.loc[trade_date, 'net_mf_vol']

        # 计算净流入金额
        net_amount = df.loc[trade_date, 'net_mf_amount']

        return (volume, amount, net_volume, net_amount)
class BasicData():
    @classmethod
    def ListDaysYears(cls):  # 关于上市天数和年数的函数
        '''
        :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
    @classmethod
    def Save_SW_Industry(cls):
        '''
        :param name_lv3: 申万三级行业的名称,数据类型,字符串
        :return: 返回一个元组,包括6个元素,分别表示申万三级行业的名称和代码:
                    name_lv1    申万一级行业的名称
                    code_lv1    申万一级行业的行业检索代码
                    name_lv2    申万二级行业的名称
                    code_lv2    申万二级行业的行业检索代码
                    name_lv3    申万三级行业的名称
                    code_lv3    申万三级行业的行业检索代码
        '''
        sw_save_path = '/BasicData/Save_SW_Industry/申万三级行业.csv'
        bool_value, final_save_path_str = Basics.IsNewDataFile(path_suffix=sw_save_path)
        if bool_value:  # 增加一个逻辑判断,如果是最新文件,则不需要重新生成文件
            print('已是最新文件,无需更新!')
        else:
            # 1.构造DataFrame,内含申万一、二、三级行业
            SW_Lv1 = pro.index_classify(level='L1', src='SW2021')  # 申万一级行业
            SW_Lv2 = pro.index_classify(level='L2', src='SW2021')  # 申万二级行业
            SW_Lv3 = pro.index_classify(level='L3', src='SW2021')  # 申万三级行业

            list1 = [i + '1' for i in SW_Lv1.columns]  # 构造一个SW_Lv1的列索引
            list2 = [i + '2' for i in SW_Lv2.columns]  # 构造一个SW_Lv2的列索引
            list3 = [i + '3' for i in SW_Lv3.columns]  # 构造一个SW_Lv3的列索引
            SW_Lv1.columns = list1  # 重新设置SW_Lv1的列索引
            SW_Lv2.columns = list2  # 重新设置SW_Lv2的列索引
            SW_Lv3.columns = list3  # 重新设置SW_Lv3的列索引
            MergeLv1_Lv2 = SW_Lv1.merge(SW_Lv2, left_on='industry_code1',
                                        right_on='parent_code2')  # 合并申万一、二级行业
            MergeLv1_Lv2_Lv3 = MergeLv1_Lv2.merge(SW_Lv3, left_on='industry_code2',
                                                  right_on='parent_code3')  # 合并申万一、二、三级行业

            # 删除多余的列并将结果赋值给一个新变量
            drop_list = ['level1', 'industry_code1', 'is_pub1', 'parent_code1',
                         'level2', 'industry_code2', 'is_pub2', 'parent_code2',
                         'level3', 'industry_code3', 'is_pub3', 'parent_code3']
            result_data = MergeLv1_Lv2_Lv3.drop(labels=drop_list, axis=1)

            Basics.PandasSaveData(data=result_data, path_suffix=sw_save_path)

    @classmethod
    def Read_SW_Industry(cls):  # 生成一个关于DataFrame分类的函数
        '''
        :return: 返回一个关于申万三级行业的DataFrame
        '''
        # 1.使用Basics.IsNewDataFile判断是否为当日的申万分类文件
        sw_read_path = '/BasicData/Save_SW_Industry/申万三级行业.csv'
        bool_value, final_save_path_str = Basics.IsNewDataFile(path_suffix=sw_read_path)
        # 2.读取申万文件中的数据
        if bool_value:
            df1 = pd.read_csv(final_save_path_str)
        else:
            BasicData.Save_SW_Industry()
            df1 = pd.read_csv(final_save_path_str)
        # 3.函数的返回值
        return df1

    @classmethod
    def Get_SW_Name_Code(cls, name_lv3):
        '''
        :param name_lv3: 申万三级行业的名称,数据类型,字符串
        :return: 返回一个元组,包括6个元素,分别表示申万三级行业的名称和代码:
                    name_lv1    申万一级行业的名称
                    code_lv1    申万一级行业的代码
                    name_lv2    申万二级行业的名称
                    code_lv2    申万二级行业的代码
                    name_lv3    申万三级行业的名称
                    code_lv3    申万三级行业的代码
        '''
        # 1.获取申万一、二、三级行业

        MergeLv1_Lv2_Lv3 = BasicData.Read_SW_Industry()
        MergeLv1_Lv2_Lv3.index = [i for i in MergeLv1_Lv2_Lv3['industry_name3'].values]

        # 2.获取一、二、三级行业对应的代码及一、二级行业对应的名称
        try: # 如果不报错则获取返回三级行业名称及代码
            name_lv1 = MergeLv1_Lv2_Lv3.loc[name_lv3, 'industry_name1']
            code_lv1 = MergeLv1_Lv2_Lv3.loc[name_lv3, 'index_code1']
            name_lv2 = MergeLv1_Lv2_Lv3.loc[name_lv3, 'industry_name2']
            code_lv2 = MergeLv1_Lv2_Lv3.loc[name_lv3, 'index_code2']
            code_lv3 = MergeLv1_Lv2_Lv3.loc[name_lv3, 'index_code3']
            return (name_lv1, code_lv1, name_lv2, code_lv2, name_lv3, code_lv3)
        except: # 如果报错则取值为nan,因为同花顺中的行业有的可能不是申万行业,比如胶黏脐及胶带
            return (np.nan,np.nan,np.nan,np.nan,np.nan,np.nan)

    @classmethod
    def Save_SW_Stock(cls):  # 该函数执行时间大约为三四分钟
        # TODO 手动补录未包含在行业分类中的股票数据,并将两份数据合并,可以采用一个月或两个检查一次是否为最新THS股票申万行业
        '''
        :return: 生成申万分类的excel文件
        '''

        SW_save_path='/BasicData/Save_SW_Stock/申万行业分类.csv'
        bool_value,final_save_path_str=Basics.IsNewDataFile(path_suffix=SW_save_path)
        if bool_value: # 增加一个逻辑判断,如果是最新文件,则不需要重新生成文件
            print('已是最新文件,无需更新!')

        else:
            # 1.构造DataFrame,内含申万一、二、三级行业
            SW_Lv1 = pro.index_classify(level='L1', src='SW2021')  # 申万一级行业
            SW_Lv2 = pro.index_classify(level='L2', src='SW2021')  # 申万二级行业
            SW_Lv3 = pro.index_classify(level='L3', src='SW2021')  # 申万三级行业

            list1 = [i + '1' for i in SW_Lv1.columns]  # 构造一个SW_Lv1的列索引
            list2 = [i + '2' for i in SW_Lv2.columns]  # 构造一个SW_Lv2的列索引
            list3 = [i + '3' for i in SW_Lv3.columns]  # 构造一个SW_Lv3的列索引
            SW_Lv1.columns = list1  # 重新设置SW_Lv1的列索引
            SW_Lv2.columns = list2  # 重新设置SW_Lv2的列索引
            SW_Lv3.columns = list3  # 重新设置SW_Lv3的列索引
            MergeLv1_Lv2 = SW_Lv1.merge(SW_Lv2, left_on='industry_code1',
                                        right_on='parent_code2')  # 合并申万一、二级行业
            MergeLv1_Lv2_Lv3 = MergeLv1_Lv2.merge(SW_Lv3, left_on='industry_code2', right_on='parent_code3')  # 合并申万一、二、三级行业

            # 2.构造DataFrame,内所有股票的申万三级行业
            df = pro.index_member(index_code='850111.SI')  # 创建一获取申万成份股的DataFrame,主要目的是为了使用TuShare库中该DataFrame的列索引
            index_code_list = SW_Lv3.loc[:, 'index_code3']  # 获取申万三级行业的所有行业指数代码index_code3
            DataFrame = pd.DataFrame(data=None, columns=df.columns)  # 创建一个空列表,列索引同df的列索引
            for i in index_code_list.values:  # 该循环执行过程较长,大约为三四分钟
                print('正在获取申万行业数据')
                df = pro.index_member(index_code=i)
                DataFrame = pd.concat([DataFrame, df], ignore_index=True)
                time.sleep(0.5)

            # 3.综合1和2的结果,构造一个DataFrame,包括所有股票的所属于的申万一、二、三级行业
            result_df1 = DataFrame.merge(MergeLv1_Lv2_Lv3, left_on='index_code', right_on='index_code3')
            # 删除"is_new"列中为N的行
            result_df1.index = result_df1.loc[:, "is_new"]
            result_df2 = result_df1.drop(labels='N', axis=0)

            # 删除不需要的列
            del_columns_list = ['index_code', 'in_date', 'out_date', 'is_new', 'index_code1', 'level1', 'industry_code1',
                                'is_pub1', 'parent_code1', 'index_code2', 'level2', 'industry_code2', 'is_pub2',
                                'parent_code2',
                                'index_code3', 'level3', 'industry_code3', 'is_pub3', 'parent_code3']
            result_df3 = result_df2.drop(labels=del_columns_list, axis=1)  # 删除t1中不需要的第0列
            SW_tscode_list = [i for i in result_df3['con_code'].values]
            # 添加股票简称列
            name_data = pro.stock_basic(exchange='', list_status='L', fields='ts_code,name')

            result_df4 = name_data.merge(result_df3, left_on='ts_code', right_on='con_code')

            # 删除result_df4不需要的con_code列
            result_df5 = result_df4.drop(labels='con_code', axis=1)

            # 设置股票代码为行索引
            index_list = [i[0:6] for i in result_df5.loc[:, 'ts_code'].values]
            result_df5.index = index_list
            # 4.将最终结果保存为csv格式文件
            final_save_path_str=Basics.PandasSaveData(data=result_df5,path_suffix=SW_save_path)
            # 5.导出未包含在申万行业分类中的股票名称
            contain_nan_df1 = name_data.merge(result_df3, left_on='ts_code', right_on='con_code',
                                             how='left')  # 包含nan的申万行业数据
            contain_nan_df2=contain_nan_df1.drop(labels='con_code', axis=1)

            def judge_isnan(t):
                if t['ts_code'] not in SW_tscode_list: # 如果股票的ts_code不在result_df3的con_code列中,即判断定为nan,即数据缺失
                    return True
                else:
                    return False

            contain_nan_df2['IsNaN']=contain_nan_df2.apply(judge_isnan,axis=1) # 增加判断nan列
            contain_nan_df3=contain_nan_df2[contain_nan_df2['IsNaN']==True] # 使用布尔筛选,选出为nan的行
            contain_nan_df4=contain_nan_df3.drop(labels=['industry_name1','industry_name2','IsNaN'], axis=1,inplace=False) # 去掉不需要的IsNan列
            contain_nan_df_path='/BasicData/Save_SW_Stock/非申万分类股票.xlsx'
            Basics.PandasSaveData(data=contain_nan_df4,path_suffix=contain_nan_df_path,save_file_type='excel')

        # 5.函数返回值
        return final_save_path_str
    @classmethod

    def Read_SW_Stock(cls):  # 生成一个关于DataFrame分类的函数
        '''
        :return: 返回一个关于申万行业的DataFrame
        '''
        # 1.使用Basics.IsNewDataFile判断是否为当日的申万分类文件
        SW_save_path = '/BasicData/Save_SW_Stock/申万行业分类.csv'
        bool_value,final_save_path_str=Basics.IsNewDataFile(path_suffix=SW_save_path)
        # 2.读取申万文件中的数据
        if bool_value:
            df1=pd.read_csv(final_save_path_str)
        else:
            BasicData.Save_SW_Stock()
            df1 = pd.read_csv(final_save_path_str)
        # 3.函数的返回值
        return df1
    @classmethod
    def Save_Manual_SW(cls,manual_excel_name):  # 手写申万行业数据转换函数
        '''
        函数说明:
            文件《非申万分类股票(更新日期2023-06-01).xlsx》中股票均为当日未包含在申万行业(从TuShare获取的申行业分类)中的股票。从同花顺手机APP
        公司概况中,抄写该股票的申万三级行业名称,并录入在该excel文件"industry_name3"列中。
            使用该函数,生成一份和excel文件同名的csv文件,将上述excel文件中的股票的申万三级行业数据补全。但是同花顺手机APP中,也会有个别股票存在没有行业分类情况的
        股票,比如688507.SH,在2023年6月2日这一天,就没有申万行业分类,生成的csv和excel文件保存在同一个文件夹中。
            手写的excel文件的保存路径为:'D:/StockAnalysis/ManualEntryFile(NoDelete)/Manual_SW/excel文件名'
        :param manual_excel_name: 需要读取的手动录入申万行业数据的excel文件的名称
        :return: 生成一份和excel文件同名的csv文件,包括excel文件所有股票股票的申万三级行业数据
        '''

        # 1.读取excel文件

        path = 'D:/StockAnalysis/ManualEntryFile(NoDelete)/Manual_SW/{}'.format(
            manual_excel_name)  # TODO 文件的路径是否需要保存为一个参数
        read_df = pd.read_excel(path)

        # 2.添加industry_name1和industry_name2列,写代码过程中发现:使用isinstance(i,t['industry_name3'])作为判断条件,会报错
        industry_name1_list = []
        industry_name2_list = []
        list1 = read_df['industry_name3'].values

        for i in list1:
            if isinstance(i, str):
                name1, _, name2, _, _, _ = BasicData.Get_SW_Name_Code(name_lv3=i)
                industry_name1_list.append(name1)
                industry_name2_list.append(name2)
            else:
                industry_name1_list.append(np.NaN)
                industry_name2_list.append(np.NaN)
        read_df['industry_name1'] = industry_name1_list
        read_df['industry_name2'] = industry_name2_list

        # 3.调整列顺序
        result_data = read_df.reindex(columns=['ts_code', 'name', 'industry_name1', 'industry_name2', 'industry_name3'])

        # 4.导出成csv文件
        save_csv_path = path[:-4]
        result_data.to_csv(save_csv_path + 'csv', encoding='utf_8_sig')
    @classmethod
    def Read_Manual_SW(cls,manual_excel_name):  # 读取手动抄写的申万行业数据
        '''
        :param manual_excel_name: 需要读取的手动录入申万行业数据的excel文件的名称
        :return: 返回一个dataframe,包含需要手动录入申万行业名称的股票的申万三级行业数据
        '''
        path = 'D:/StockAnalysis/ManualEntryFile(NoDelete)/Manual_SW/{}'.format(manual_excel_name)
        read_csv_path = path[:-4] + 'csv'
        if os.path.exists(read_csv_path):
            df = pd.read_csv(read_csv_path)
            result = df.drop(labels='Unnamed: 0', axis=1)
            return result
        else:
            BasicData.Save_Manual_SW(manual_excel_name=manual_excel_name)
            df = pd.read_csv(read_csv_path)
            result = df.drop(labels='Unnamed: 0', axis=1)
            return result
    @classmethod
    def Get_Whole_SW(cls):
        # 1.获取申万自动版和手动版数据以及股票名称列表
        df1 = BasicData.Read_SW_Stock()  # 自动获取的申万行业数据
        df1.index = [i for i in df1['ts_code'].values]

        df2 = BasicData.Read_Manual_SW(manual_excel_name='非申万分类股票(更新日期2023-06-01).xlsx')  # 手动录入的申万行业数据
        df2.index = [i for i in df2['ts_code'].values]

        data = pro.stock_basic(exchange='', list_status='L', fields='ts_code,name')

        # 2.构造一个获取申万三级行业名称的内置函数:优先使用自动版的申万行业分类数据
        def get_sw(ts_code):
            try:
                if ts_code in df1['ts_code'].values:
                    result = (df1.loc[ts_code, 'industry_name1'],
                              df1.loc[ts_code, 'industry_name2'],
                              df1.loc[ts_code, 'industry_name3'])
                    return result
                else:
                    result = (df2.loc[ts_code, 'industry_name1'],
                              df2.loc[ts_code, 'industry_name2'],
                              df2.loc[ts_code, 'industry_name3'])
                    return result
            except:
                result = (np.NaN, np.NaN, np.NaN)
                return result

        # 3.构造申万三级行业名称列表
        name1 = []
        name2 = []
        name3 = []
        for i in data['ts_code'].values:
            n1, n2, n3 = get_sw(ts_code=i)
            name1.append(n1)
            name2.append(n2)
            name3.append(n3)
        # 4.向data中添加申万三级行业名称列
        data['industry_name1'] = name1
        data['industry_name2'] = name2
        data['industry_name3'] = name3

        # 5.函数返回值
        return data
class Price():

    @classmethod
    def GetDaily(cls,symbol, pro_num):  # 获取某只股票自上市以来的所有交易日的日线行情
        '''
        :param symbol: 股票的代码,数据类型为字符串
        :param pro_num: A股日线行情接口每分钟允许访问的次数。5000积分时,每分钟为500次
        :return:返回一个元组,该元组包含两个变量:
                mydaily2:股票自上市以来所有交易日的日线价格,即日线行情
                running_time:函数的运行时间
        '''

        begin_time = datetime.datetime.now()  # 为计算函数运行时间而设置的变量
        sleep_t = round(1.02 * 60 / pro_num, 6)  # 为防止报错,设置的循环获取数据时的休息时间
        # 获取上市公司的上市天数和年数信息
        data, _ = ListDaysYears()

        data.index = [i for i in data['symbol'].values]
        TS_Code = data.loc[symbol, 'ts_code']
        list_days_num = data.loc[symbol, 'list_days']

        # 对于上市时间特别久的股票,构造获取日线行情的日期分段表
        length = list_days_num // 8350  # 日线行情接口一次可以获取6000条数据,即6000个交易日中的数据。但是6000个交易日对应于8390个自然日,即不少于8350个自然日
        List = []
        List.append(Today_Date_Count())
        for i in range(length):
            t = 8350 * (i + 1)
            List.append(Today_Date_Count(-(t - 1)))
            List.append(Today_Date_Count(-t))
        else:
            list_date = data.loc[symbol, 'list_date']
            List.append(list_date)
        Len = len(List)  # 列表List中元素的个数

        if length == 0:
            my_daily1 = pro.daily(ts_code=TS_Code)
            time.sleep(sleep_t)
            # 为计算函数运行时间而设置的辅助代码
            end_time = datetime.datetime.now()
            running_time = end_time - begin_time
            myresult = (my_daily1, running_time)
            return myresult
        elif length > 0:
            mydaily2 = pd.DataFrame(data=None)
            for i in range(Len // 2):  # 取Len//2的结果,为了生成(0,1)、(2,3)、(3,4)......这样的相邻奇数偶数成对出现的数据
                mydf = pro.daily(ts_code=TS_Code, start_date=List[2 * i + 1], end_date=List[2 * i])
                mydaily2 = pd.concat([mydaily2, mydf]).reset_index(drop=True)
                time.sleep(sleep_t)
            else:
                end_time = datetime.datetime.now()
                running_time = end_time - begin_time
                myresult = (mydaily2, running_time)
                return myresult

    @classmethod
    def DailyQuantile(cls,pro_num):  # 获取所有股票日线行情统计的最小值,分位值及最大值,由历史版本230525中的QuantileDaily函数升级而来
        '''
        函数评价:自认为是学编程以来写得最完美的一个函数,该函数的运行时间大概2~3小时
        :param pro_num:A股日线行情接口每分钟允许访问的次数。5000积分时,每分钟为500次
        :return:生成一个包含A股所有股票的日线收盘价分位数文件,文件名中包含函数的运行时间
        说明:
            1.today_close和position为nan或在csv文件中为空值,则有表示股票有可能处于停牌状态
            2.如果除股票名称和代码外,其他数据均为nan,可能表示该股票即将上市,还没有收盘价信息
        '''
        start_time = datetime.datetime.now()  # 计算运行时间的辅助代码
        recently_trade_date=Basics.RecentlyTradeDate()
        # 设置保存路径
        Daily_Save_path='/Price/DailyQuantile/交易日收盘价分位数.csv'
        bool_value,final_save_path_str=Basics.IsNewDataFile(path_suffix=Daily_Save_path)
        if bool_value: # 增加一个逻辑判断,如果是最新文件,则不需要重新生成文件
            print('已是最新文件,无需更新!')
            dailyquantile_df=pd.read_csv(final_save_path_str)
            return dailyquantile_df
        else:

            # 1.获取A股所有股票的代码
            data = pro.stock_basic(exchange='', list_status='L', fields='ts_code,symbol,name,list_date')
            data.sort_values(by=['symbol'], ascending=True, inplace=True)

            symbol_list = [i for i in data.loc[:, 'symbol'].values]
            data.index = symbol_list

            # 2.最近一个日期的A股所有日线行情

            today_df = pro.daily(trade_date=recently_trade_date)  # 最近交易日的所有A股的日线行情
            today_df.index = [i[:6] for i in today_df.loc[:, 'ts_code'].values]

            # 3.获得股票的日线行情
            List1 = []
            global QuantileDailyData196789
            for i in symbol_list:
                try:

                    Daily, _ = Price.GetDaily(symbol=i, pro_num=pro_num)
                    close_price = Daily.loc[:, 'close']
                    my_trade_date_list = [i for i in Daily['trade_date'].values]

                    Min = close_price.min()  # 股票历史收盘价的最小值
                    Q01 = round(close_price.quantile(0.01), 2)  # 股票历史收盘价的0.01分位数
                    Q02 = round(close_price.quantile(0.02), 2)  # 股票历史收盘价的0.02分位数
                    Q05 = round(close_price.quantile(0.05), 2)  # 股票历史收盘价的0.05分位数
                    Q10 = round(close_price.quantile(0.10), 2)  # 股票历史收盘价的0.10分位数
                    Q20 = round(close_price.quantile(0.20), 2)  # 股票历史收盘价的0.20分位数
                    Q30 = round(close_price.quantile(0.30), 2)  # 股票历史收盘价的0.30分位数
                    Q50 = round(close_price.quantile(0.50), 2)  # 股票历史收盘价的0.50分位数
                    Q70 = round(close_price.quantile(0.70), 2)  # 股票历史收盘价的0.70分位数
                    Q90 = round(close_price.quantile(0.90), 2)  # 股票历史收盘价的0.90分位数
                    Max = close_price.max()  # 股票历史收盘价的最大值

                    # 设置一个条件语句,计算最近交易日的收盘价及其在历史收盘价中的地位,如果最近交日有收盘价,则计算,否则 为Nan
                    if recently_trade_date in my_trade_date_list:
                        Q_List = [Min, Q01, Q02, Q05, Q10, Q20, Q30, Q50, Q70, Q90, Max]
                        today_close = today_df.loc[i, 'close']  # 今日收盘价
                        bool_list = []  # 用于收集今日收盘价是否小于某个分位点的列表,表中元素Y表示小于分位点,否则为N
                        for j in Q_List:
                            if j < today_close:
                                bool_list.append('Y')
                            else:
                                bool_list.append('N')
                        pos_num = bool_list.index('N')  # boollist第一个N所在的位置
                        str_list = ['min', 'Q01', 'Q02', 'Q05', 'Q10', 'Q20', 'Q30', 'Q50', 'Q70', 'Q90', 'max']  # 分位标识符列表
                        position = str_list[pos_num]  # 今日收盘价所在点位
                    else:
                        today_close = np.NaN
                        position = np.NaN

                    # 计算上市年数
                    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)  # 上市时间年数

                    # 由相关元素构成的字典
                    Dict1 = {'ts_code': data.loc[i, 'ts_code'],
                             'name': data.loc[i, 'name'],
                             'year': ListYearNums,
                             'today_close': today_close,
                             'position': position,
                             'min': Min,
                             'Q01': Q01,
                             'Q02': Q02,
                             'Q05': Q05,
                             'Q10': Q10,
                             'Q20': Q20,
                             'Q30': Q30,
                             'Q50': Q50,
                             'Q70': Q70,
                             'Q90': Q90,
                             'max': Max
                             }
                    print(Dict1)

                    # 生成DataFrame

                    List1.append(Dict1)
                    QuantileDailyData196789 = pd.DataFrame(List1)

                except:
                    Dict1 = {'ts_code': data.loc[i, 'ts_code'],
                             'name': data.loc[i, 'name'],
                             'year': np.NaN,
                             'today_close': np.NaN,
                             'position': np.nan,
                             'min': np.NaN,
                             'Q05': np.NaN,
                             'Q10': np.NaN,
                             'Q20': np.NaN,
                             'Q30': np.NaN,
                             'Q50': np.NaN,
                             'Q70': np.NaN,
                             'Q90': np.NaN,
                             'max': np.NaN
                             }
                    print(Dict1)
                    List1.append(Dict1)
                    QuantileDailyData196789 = pd.DataFrame(List1)

        # 计算程序运行时间的辅助代码
        end_tine = datetime.datetime.now()
        running_time = end_tine - start_time
        print(running_time)
        # 导出数据及函数返回值
        Basics.PandasSaveData(data=QuantileDailyData196789, path_suffix=Daily_Save_path)
        return QuantileDailyData196789
    @classmethod
    def GetDailyTarget(cls,symbol,target,pro_num):
        '''
        该函数参考Price.GetDaily()书写而来
        :param symbol: 股票的代码,数据类型为字符串
        :param pro_num: A股日线行情接口每分钟允许访问的次数。5000积分时,每分钟为500次
        :return:返回一个元组,该元组包含两个变量:
                mydaily2:股票自上市以来所有交易日的每日指标,即每日行情指标
                running_time:函数的运行时间
        '''

        begin_time = datetime.datetime.now()  # 为计算函数运行时间而设置的变量
        sleep_t = round(1.02 * 60 / pro_num, 6)  # 为防止报错,设置的循环获取数据时的休息时间
        # 获取上市公司的上市天数和年数信息
        data, _ = ListDaysYears()

        data.index = [i for i in data['symbol'].values]
        TS_Code = data.loc[symbol, 'ts_code']
        list_days_num = data.loc[symbol, 'list_days']

        # 对于上市时间特别久的股票,构造获取日线行情的日期分段表
        length = list_days_num // 8350  # 日线行情接口一次可以获取6000条数据,即6000个交易日中的数据。但是6000个交易日对应于8390个自然日,即不少于8350个自然日
        List = []
        List.append(Today_Date_Count())
        for i in range(length):
            t = 8350 * (i + 1)
            List.append(Today_Date_Count(-(t - 1)))
            List.append(Today_Date_Count(-t))
        else:
            list_date = data.loc[symbol, 'list_date']
            List.append(list_date)
        Len = len(List)  # 列表List中元素的个数

        if length == 0:
            my_daily1 = pro.daily_basic(ts_code=TS_Code,fields='ts_code,trade_date,{}'.format(target))
            time.sleep(sleep_t)
            # 为计算函数运行时间而设置的辅助代码
            end_time = datetime.datetime.now()
            running_time = end_time - begin_time
            myresult = (my_daily1, running_time)
            return myresult
        elif length > 0:
            mydaily2 = pd.DataFrame(data=None)
            for i in range(Len // 2):  # 取Len//2的结果,为了生成(0,1)、(2,3)、(3,4)......这样的相邻奇数偶数成对出现的数据
                # mydf = pro.daily(ts_code=TS_Code, start_date=List[2 * i + 1], end_date=List[2 * i])
                mydf = pro.daily_basic(ts_code=TS_Code,start_date=List[2 * i + 1], end_date=List[2 * i],
                                     fields='ts_code,trade_date,{}'.format(target))
                mydaily2 = pd.concat([mydaily2, mydf]).reset_index(drop=True)
                time.sleep(sleep_t)
            else:
                end_time = datetime.datetime.now()
                running_time = end_time - begin_time
                myresult = (mydaily2, running_time)
                return myresult


    @classmethod
    def DailyTargetQuantile(cls,target='pe'): # 股价每日指标分位数函数
        '''
            由历史版本230525中Quantile_Day_Data_Vision_F()升级而来
            该函数大概运行2小时左右
            :param target:  指标代码
                该参数的值:
                    pe  市盈率(总市值/净利润, 亏损的PE为空)
                    pb  市净率(总市值/净资产)
                    ps  市销率
                    turnover_rate   换手率
                    volume_ratio    量比
            :return: 返回一张A股所有股票的财务指标统计表。若某只股票的指标数据为nan,表明连续两次读取该股票数据时出现了错误。
            '''
        start_time = datetime.datetime.now()  # 计算函数运行时间的辅助代码
        # 验证是否为最新文件且是否存在
        DailyTargetQuantile_Save_Path='/Price/DailyTargetQuantile/每日指标{}分位数.csv'.format(target)
        bool_value,final_save_path_str=Basics.IsNewDataFile(path_suffix=DailyTargetQuantile_Save_Path)
        if bool_value: # 增加一个逻辑判断,如果是最新文件,则不需要重新生成文件
            print('已是最新文件,无需更新!')
        else:
            # 获得A股票所有股票ts码
            data = pro.stock_basic(exchange='', list_status='L', fields='ts_code,symbol,name')
            ts_code = [i for i in data.loc[:, 'ts_code'].values]
            data.index = ts_code

            # 1.获得每日日交易指标
            List1 = []
            global daily_target_data125687912
            for i in ts_code:
                try:
                    my_symbol=i[0:6]
                    # df = pro.daily_basic(ts_code=i, fields='ts_code,trade_date,{}'.format(target))  # TODO 该数据需要定义一个函数获取
                    df,_=Price.GetDailyTarget(symbol=my_symbol,target=target,pro_num=500)
                    s1 = df['{}'.format(target)]
                    # 计算分位数指标
                    Min = round(s1.min(), 2)
                    Q01 = round(s1.quantile(0.01), 2)
                    Q02 = round(s1.quantile(0.02), 2)
                    Q05 = round(s1.quantile(0.05), 2)
                    Q10 = round(s1.quantile(0.10), 2)
                    Q20 = round(s1.quantile(0.20), 2)
                    Q30 = round(s1.quantile(0.30), 2)
                    Q50 = round(s1.quantile(0.50), 2)
                    Q70 = round(s1.quantile(0.70), 2)
                    Q90 = round(s1.quantile(0.90), 2)
                    Max = round(s1.max(), 2)

                    value_list = [Min, Q01, Q02, Q05, Q10, Q20, Q30, Q50, Q70, Q90, Max]
                    str_list = ['min', 'Q01', 'Q02', 'Q05', 'Q10', 'Q20', 'Q30', 'Q50', 'Q70', 'Q90', 'max']

                    # 计算当日指标及其所在位置

                    today_target = round(df.loc[0, '{}'.format(target)], 2)

                    bool_list = []
                    for j in value_list:
                        if j < today_target:
                            bool_list.append('Y')
                        else:
                            bool_list.append('N')

                    if np.isnan(today_target):
                        global position
                        position = np.NaN

                    else:
                        pos_num = bool_list.index('N')
                        position = str_list[pos_num]

                    dict1 = {'ts_code': i,
                             'name': data.loc[i, 'name'],
                             'today_target': today_target,
                             'position': position,
                             'min': Min,
                             'Q01': Q01,
                             'Q02': Q02,
                             'Q05': Q05,
                             'Q10': Q10,
                             'Q20': Q20,
                             'Q30': Q30,
                             'Q50': Q50,
                             'Q70': Q70,
                             'Q90': Q90,
                             'max': Max
                             }
                    print(dict1)
                    List1.append(dict1)
                    daily_target_data125687912 = pd.DataFrame(List1)

                except:
                    dict1 = {'ts_code': i,
                             'name': data.loc[i, 'name'],
                             'today_target': np.NaN,
                             'position': np.NaN,
                             'min': np.NaN,
                             'Q01': np.NaN,
                             'Q02': np.NaN,
                             'Q05': np.NaN,
                             'Q10': np.NaN,
                             'Q20': np.NaN,
                             'Q30': np.NaN,
                             'Q50': np.NaN,
                             'Q70': np.NaN,
                             'Q90': np.NaN,
                             'max': np.NaN
                             }
                    print(dict1)
                    List1.append(dict1)
                    daily_target_data125687912 = pd.DataFrame(List1)

            else:
                Basics.PandasSaveData(data=daily_target_data125687912,path_suffix=DailyTargetQuantile_Save_Path)
        # 计算函数运行时间的辅助代码
        end_time = datetime.datetime.now()
        running_time = end_time - start_time
        print(running_time)
    @classmethod
    def DailyTargetQuantile_SW(cls,target, func):
        '''
        本函数由20230525版本中Get_SW_Symbol_FinancialTarget()函数升级而来
        :param target: 财务指标名称,可选择的参数值为
                        pe  市盈率(总市值/净利润, 亏损的PE为空)
                        pb  市净率(总市值/净资产)
                        ps  市销率
                        默认值为pb
        :param func: 统计行业指标的函数参数,其值可以为两个:mean,quantile
                    使用参数值mean,即使用统计函数mean()时,q参数可以不用填写。
                    但是使用参数值quantile,即使用统计函数quantile时,需要配合q参数使用
        :param q:分位数函数quantile()的分数的分位值。本参数在下一个版本中再进行考虑使用
        :return:1.返回值是一个带有复合索引的DataFrame即result_data,其复合索引分别为by=['industry_name1','industry_name2','industry_name3']
                  其他列索引名称格式为:分位值名称_分位数函数名称。
                  比如:
                    Q10_mean,表示某个行业股票指标10%分位数,对应的行业平均值
                    Q10_quantile50.0%,表示某个行业股票指标10%分位数,对应的行业的分位数为0.5即50%的值,表示中位数
                2.将result_data导出成csv文件
                3.返回值result_data
        '''

        # 1.获取申万行业数据和DailyTargetQuantile数据
        sw_data = BasicData.Get_Whole_SW()
        sw_data.index = [i[0:6] for i in sw_data['ts_code'].values]
        DailyTargetQuantile_Save_Path = '/Price/DailyTargetQuantile/每日指标{}分位数.csv'.format(target)
        # bool_value, final_save_path_str = Basics.IsNewDataFile(path_suffix=DailyTargetQuantile_Save_Path)
        # 检查Price.DailyTargetQuantile()生成的文件是否为最新,若为最新,则读取,否则执行该函数

        bool_value, final_save_path_str = Basics.IsNewDataFile(path_suffix=DailyTargetQuantile_Save_Path)

        if bool_value:  # 增加一个逻辑判断,如果是最新文件,则不需要重新生成文件
            print('已是最新文件,无需更新!')
        else:
            Price.DailyTargetQuantile(target=target)
        data = pd.read_csv(final_save_path_str)

        data.index = [i[0:6] for i in data['ts_code'].values]

        # 2.将data中不需要的列删除,并赋值给新的变量
        drop_data = data.drop(columns=['ts_code', 'name', 'today_target', 'position'])

        join_data = sw_data.join(drop_data)

        # 3.使用分组函数
        groupby1 = join_data.groupby(by=['industry_name1', 'industry_name2', 'industry_name3'])

        # 4.使用循环获得数据,比20230525版本中函数的方法Get_SW_Symbol_FinancialTarget()更优
        col_list = [i for i in join_data.columns.values]
        use_variate_list = col_list[-11:]

        # 定义保存文件的路径后缀,并判断函数是否需要更新
        save_path_suffix01892651 = f'/Price/DailyTargetQuantile_SW/申万行业{target}统计{func}.csv'
        bool_value_this_func, final_save_path_str_this_func = Basics.IsNewDataFile(path_suffix=save_path_suffix01892651)

        if bool_value_this_func:  # 增加一个逻辑判断,如果是最新文件,则不需要重新生成文件
            print('DailyTargetQuantile_SW生成的文件已是最新文件,无需更新!')
            df = pd.read_csv(final_save_path_str_this_func) # 读取文件中的数据
            # t=df.groupby(by=['industry_name1','industry_name2','industry_name3'])
            t = df.set_index(['industry_name1', 'industry_name2', 'industry_name3']) # 设置复合索引并赋值给新变量

            return t
        else:

            if func == 'mean':
                list1 = []  # 列名称的列表
                list2 = []  # 列的数值列表
                for i in use_variate_list:
                    str1 = f'{i}_行业均值'
                    value = round(groupby1[f'{i}'].mean(), 2)
                    list1.append(str1)
                    list2.append(value)
                else:

                    result_data = pd.DataFrame({f'{list1[0]}': list2[0],
                                                f'{list1[1]}': list2[1],
                                                f'{list1[2]}': list2[2],
                                                f'{list1[3]}': list2[3],
                                                f'{list1[4]}': list2[4],
                                                f'{list1[5]}': list2[5],
                                                f'{list1[6]}': list2[6],
                                                f'{list1[7]}': list2[7],
                                                f'{list1[8]}': list2[8],
                                                f'{list1[9]}': list2[9],
                                                f'{list1[10]}': list2[10]})

                    # 导出数据
                    Basics.PandasSaveData(data=result_data, path_suffix=save_path_suffix01892651, index=True)
                    # 函数返回值
                    print(result_data)
                    return result_data
            elif func == 'quantile':
                q = 0.5  # TODO 此变量可以设置为参数,下次升级是否添加此功能,有待考试
                list1 = []  # 列名称的列表
                list2 = []  # 列的数值列表
                for i in use_variate_list:
                    str1 = f'{i}_行业中位数'
                    value = round(groupby1[f'{i}'].quantile(q), 2)
                    list1.append(str1)
                    list2.append(value)
                else:

                    result_data = pd.DataFrame({f'{list1[0]}': list2[0],
                                                f'{list1[1]}': list2[1],
                                                f'{list1[2]}': list2[2],
                                                f'{list1[3]}': list2[3],
                                                f'{list1[4]}': list2[4],
                                                f'{list1[5]}': list2[5],
                                                f'{list1[6]}': list2[6],
                                                f'{list1[7]}': list2[7],
                                                f'{list1[8]}': list2[8],
                                                f'{list1[9]}': list2[9],
                                                f'{list1[10]}': list2[10]})

                    # 导出数据
                    Basics.PandasSaveData(data=result_data, path_suffix=save_path_suffix01892651, index=True)
                    # 函数返回值
                    print(result_data)
                    return result_data
    @classmethod
    def Get_SW_DailyTargetQuantileResult(cls,name_lv3, data_column, func, target='pb'):
        '''
        本函数由20230525版中函数Get_SW_Symbol_FinancialTarget()升级而来
        使用该函数的运行结果,同分位同统计函数的运行结果两相比较,取其较小值为筛选股票的标准,应该有效果。
        :param name_lv3: SW三级行业的名称,数据类型为字符串
        :param data_column: Price.DailyTargetQuantile_SW返回值或生成的csv文件中的统计分位数的列名称。
                            min 表示分位值的最小值,也就是指标的最小值
                            Q01 表示指标分位为1%时对应的数值
                            Q02~Q90 含义同Q01
                            max 表示分位值的最小值,也就是指标的最小值
        :param func: 统计行业指标的函数名称,其值可以为两个:mean,quantile。若不明白请见Price.DailyTargetQuantile_SW中参数解释。
        :param target: 财务指标名称,可选择的参数值为
                        pe  市盈率(总市值/净利润, 亏损的PE为空)
                        pb  市净率(总市值/净资产)
                        ps  市销率
                        默认值为pb
        :return: 申万三级行业对应的某个指标的分位数的统计均值或中位数。
        '''
        '''
        本函数由20230525版中函数Get_SW_Symbol_FinancialTarget()升级而来
        使用该函数的运行结果,同分位同统计函数的运行结果两相比较,取其较小值为筛选股票的标准,应该有效果。
        :param name_lv3: SW三级行业的名称,数据类型为字符串
        :param data_column: 参数值范围:min_mean,Q10_mean,Q25_mean,Q50_mean,Q75_mean,Q90_mean,max_mean。
                        含义为SW行业各三级行业所包含股票对应财务指标的min,Q10,Q25,Q50,Q75,Q90,max的平均值。
                        其中Q10,Q25,Q50,Q75,Q90分别为对应的10%,25%,50%,75%,90%分位数。如果实在记不清楚,
                        可以查看'./LoadFile/SWFinancialTargetStatistics/SW财务{}统计结果.csv'.format(target)
                        中相应文件,看一下列索引就容易明白该参数的含义了。
        :param target:  财务指标名称,可选择的参数值为
                        pe  市盈率(总市值/净利润, 亏损的PE为空)
                        pb  市净率(总市值/净资产)
                        ps  市销率
                        默认值为pb
        :return:
        '''
        # 1.处理SW行业数据
        df_l1 = df = pro.index_classify(level='L1', src='SW2021')
        df_l1.index = df_l1.loc[:, 'industry_code']
        df_l2 = df = pro.index_classify(level='L2', src='SW2021')
        df_l2.index = df_l2.loc[:, 'industry_code']
        df_l3 = df = pro.index_classify(level='L3', src='SW2021')
        df_l3.index = df_l3.loc[:, 'industry_name']
        # 2.获取SW一、二级行业的名称

        L2_Code = df_l3.loc[name_lv3, 'parent_code']
        L2_Name = df_l2.loc[L2_Code, 'industry_name']

        L1_Code = df_l2.loc[L2_Code, 'parent_code']
        L1_Name = df_l1.loc[L1_Code, 'industry_name']

        # 3.获得行业指标统计结果
        data = Price.DailyTargetQuantile_SW(target=target, func=func)
        if func == 'mean':
            c_index_str = data_column + '_行业平均值'
            result = data.loc[L1_Name].loc[L2_Name].loc[name_lv3][c_index_str]
            return result
        elif func == 'quantile':
            c_index_str = data_column + '_行业中位数'
            result = data.loc[L1_Name].loc[L2_Name].loc[name_lv3][c_index_str]
            return result

class Upgrade():
    @classmethod
    def GetDailyTarget(cls, symbol, target_list: list, pro_num):
        '''
        :param symbol: 股票的代码,数据类型为字符串
        :param target_list: 一个列表,包含指标符号,指标符号可以为多个值
        :param pro_num: A股日线行情接口每分钟允许访问的次数。5000积分时,每分钟为500次
        :return: 返回一个元组,该元组包含两个变量:
                mydaily2:股票自上市以来所有交易日的每日指标,即每日行情指标
                running_time:函数的运行时间
        经验总结:
            1.将原20230530版本Price.GetDailyTarget()的target参数,设置为列表,即新版本中
        参数改为target_list,这样生成的dataframe获取可一次获取多个技术指标。此方法已经成功。
            2.由本函数可以发现,不定长参数*args和**kwargs可以理解为表示由变量组成的元组和字典。
        同本函数中的target_list参数的用途中致的。
        '''


        begin_time = datetime.datetime.now()  # 为计算函数运行时间而设置的变量
        sleep_t = round(1.02 * 60 / pro_num, 6)  # 为防止报错,设置的循环获取数据时的休息时间
        # 获取上市公司的上市天数和年数信息
        data, _ = ListDaysYears()

        data.index = [i for i in data['symbol'].values]
        TS_Code = data.loc[symbol, 'ts_code']
        list_days_num = data.loc[symbol, 'list_days']

        # 对于上市时间特别久的股票,构造获取日线行情的日期分段表
        length = list_days_num // 8350  # 日线行情接口一次可以获取6000条数据,即6000个交易日中的数据。但是6000个交易日对应于8390个自然日,即不少于8350个自然日
        List = []
        List.append(Today_Date_Count())
        for i in range(length):
            t = 8350 * (i + 1)
            List.append(Today_Date_Count(-(t - 1)))
            List.append(Today_Date_Count(-t))
        else:
            list_date = data.loc[symbol, 'list_date']
            List.append(list_date)
        # 使用循环定义fields变量
        fields = 'ts_code,trade_date'
        for i in target_list:
            fields = fields + ',{}'.format(i)

        Len = len(List)  # 列表List中元素的个数
        if length == 0:
            my_daily1 = pro.daily_basic(ts_code=TS_Code, fields=fields)
            time.sleep(sleep_t)
            # 为计算函数运行时间而设置的辅助代码
            end_time = datetime.datetime.now()
            running_time = end_time - begin_time
            myresult = (my_daily1, running_time)
            return myresult
        elif length > 0:
            mydaily2 = pd.DataFrame(data=None)
            for i in range(Len // 2):  # 取Len//2的结果,为了生成(0,1)、(2,3)、(3,4)......这样的相邻奇数偶数成对出现的数据
                # mydf = pro.daily(ts_code=TS_Code, start_date=List[2 * i + 1], end_date=List[2 * i])
                mydf = pro.daily_basic(ts_code=TS_Code, start_date=List[2 * i + 1], end_date=List[2 * i],
                                       fields=fields)
                mydaily2 = pd.concat([mydaily2, mydf]).reset_index(drop=True)
                time.sleep(sleep_t)
            else:
                end_time = datetime.datetime.now()
                running_time = end_time - begin_time
                myresult = (mydaily2, running_time)
                return myresult

    @classmethod
    def DailyTargetQuantile(cls,target_list: list):  # 股价每日指标分位数函数
        '''
            由历史版本230525中Quantile_Day_Data_Vision_F()升级而来
            该函数大概运行2小时左右
            :param target:  指标代码
                该参数的值:
                    pe  市盈率(总市值/净利润, 亏损的PE为空)
                    pb  市净率(总市值/净资产)
                    ps  市销率
                    turnover_rate   换手率
                    volume_ratio    量比
            :return: 返回一张A股所有股票的财务指标统计表。若某只股票的指标数据为nan,表明连续两次读取该股票数据时出现了错误。
            '''

        start_time = datetime.datetime.now()  # 计算函数运行时间的辅助代码
        # 获得A股票所有股票ts码
        data = pro.stock_basic(exchange='', list_status='L', fields='ts_code,symbol,name')
        ts_code = [i for i in data.loc[:, 'ts_code'].values]
        data.index = ts_code
        # 判断文件是否需要更新
        bool_list = []
        Lenght = len(target_list)
        # print(Lenght)
        for i in range(Lenght):
            T = target_list[i]
            # print(T, type(T))
            bool_path_suffix = '/Upgrade/DailyTargetQuantile/每日指标{}的分位数.csv'.format(T)
            bool_value, _ = Basics.IsNewDataFile(path_suffix=bool_path_suffix)
            if bool_value:
                bool_list.append(True)
            else:
                bool_list.append(False)
        if False not in bool_list:
            print('所有指标均为当日指标,不需要更新')
        else:
            print('函数正在运行......')

            # 使用参数列表mylist中的参数命名一些变量,并将这些变量添加至一个列表variate_name_str中
            variate_name_str = []  # 变量名称列表
            for i in target_list:
                v_str = i + '_' + 'list'
                variate_name_str.append(v_str)
            # 将variate_name_str中的变量赋值为空列表,并添加至一个新列表variate_name_list中

            variate_name_list = []
            for i in variate_name_str:
                i = []
                variate_name_list.append(i)

            # 1.获得每日日交易指标

            for i in ts_code:
                my_symbol = i[0:6]

                # df, _ = Price.GetDailyTarget(symbol=my_symbol, target_list=target_list, pro_num=500)
                df, _ = Upgrade.GetDailyTarget(symbol=my_symbol, target_list=target_list, pro_num=500)
                for j in range(Lenght):
                    target = target_list[j]
                    variate_list = variate_name_list[j]
                    try:
                        s1 = df['{}'.format(target)]
                        # 计算分位数指标
                        Min = round(s1.min(), 2)
                        Q01 = round(s1.quantile(0.01), 2)
                        Q02 = round(s1.quantile(0.02), 2)
                        Q05 = round(s1.quantile(0.05), 2)
                        Q10 = round(s1.quantile(0.10), 2)
                        Q20 = round(s1.quantile(0.20), 2)
                        Q30 = round(s1.quantile(0.30), 2)
                        Q50 = round(s1.quantile(0.50), 2)
                        Q70 = round(s1.quantile(0.70), 2)
                        Q90 = round(s1.quantile(0.90), 2)
                        Max = round(s1.max(), 2)

                        value_list = [Min, Q01, Q02, Q05, Q10, Q20, Q30, Q50, Q70, Q90, Max]

                        str_list = ['min', 'Q01', 'Q02', 'Q05', 'Q10', 'Q20', 'Q30', 'Q50', 'Q70', 'Q90', 'max']

                        # 计算当日指标及其所在位置

                        today_target = round(df.loc[0, '{}'.format(target)], 2)

                        bool_list = []
                        for j in value_list:
                            if j < today_target:
                                bool_list.append('Y')
                            else:
                                bool_list.append('N')

                        if np.isnan(today_target):
                            global position
                            position = np.NaN

                        else:
                            pos_num = bool_list.index('N')
                            position = str_list[pos_num]

                        dict1 = {'ts_code': i,
                                 'name': data.loc[i, 'name'],
                                 'today_target': today_target,
                                 'position': position,
                                 'min': Min,
                                 'Q01': Q01,
                                 'Q02': Q02,
                                 'Q05': Q05,
                                 'Q10': Q10,
                                 'Q20': Q20,
                                 'Q30': Q30,
                                 'Q50': Q50,
                                 'Q70': Q70,
                                 'Q90': Q90,
                                 'max': Max
                                 }
                        print(dict1)
                        variate_list.append(dict1)

                    except:
                        dict1 = {'ts_code': i,
                                 'name': data.loc[i, 'name'],
                                 'today_target': np.NaN,
                                 'position': np.NaN,
                                 'min': np.NaN,
                                 'Q01': np.NaN,
                                 'Q02': np.NaN,
                                 'Q05': np.NaN,
                                 'Q10': np.NaN,
                                 'Q20': np.NaN,
                                 'Q30': np.NaN,
                                 'Q50': np.NaN,
                                 'Q70': np.NaN,
                                 'Q90': np.NaN,
                                 'max': np.NaN
                                 }
                        print(dict1)
                        variate_list.append(dict1)

            for i in range(Lenght):
                list1 = variate_name_list[i]
                df1 = pd.DataFrame(list1)
                T = target_list[i]
                Basics.PandasSaveData(data=df1, path_suffix='/Upgrade/DailyTargetQuantile/每日指标{}的分位数.csv'.format(T))

        end_time = datetime.datetime.now()
        running_time = end_time - start_time
        print(running_time)
    @classmethod
    def Forecast(cls,start_date, end_date, Quarter): # 这是Financial.Forecast()的原始函数,封装前的函数的构思思路都在这里,可以在该函数的基础上升级该函数,因此特意保留该段代码
        '''
        :param start_date: 预测的开始日期
        :param end_date: 预测的结束日期
        :param Quarter: 预测的报告期,比如2022Q4表示2022年4季度
        :return:
        '''

        # 定义将日期字符串转化为日期的函数
        def turn_str_to_date(date_str):
            y_int = eval(date_str[0:4])
            m_int = eval(date_str[4:6].strip('0'))
            d_int = eval(date_str[6:8].strip('0'))

            turn_date = datetime.date(year=y_int, month=m_int, day=d_int)

            return turn_date

        # 使用定义的内部函数
        start = turn_str_to_date(date_str=start_date)
        end = turn_str_to_date(date_str=end_date)
        # 创建日期列表
        date_list = []
        mydate = start
        date_list.append(start_date)
        for i in range(12500):
            mydate = mydate + datetime.timedelta(days=1)
            if mydate < end:
                mydate_str = mydate.__str__()
                new_date_str = mydate_str.replace('-', '')
                date_list.append(new_date_str)
            elif mydate == end:
                mydate_str = mydate.__str__()
                new_date_str = mydate_str.replace('-', '')
                date_list.append(new_date_str)
                break
        print(date_list)
        # 4.获取预测数据
        forecast_df = pd.DataFrame()
        for i in date_list:
            print(i)
            df = pro.report_rc(ts_code='', report_date=i)
            forecast_df = pd.concat([forecast_df, df])
        forecast_df.index = [i for i in range(forecast_df['ts_code'].count())]
        print(forecast_df)
        y = forecast_df.loc[0, 'quarter']
        print(y, type(y))

        def AheadDays(t):  # 计算报告期同预测日期的相差天数的函数,暂时不使用
            if t['quarter'][-1] == '1':
                str1 = t['quarter'][0:4] + '0331'
                str_to_date = turn_str_to_date(date_str=str1)
                report_date = turn_str_to_date(date_str=t['report_date'])
                differ_day = str_to_date - report_date
                days = differ_day.days
                return days
            elif t['quarter'][-1] == '2':
                str1 = t['quarter'][0:4] + '0630'
                str_to_date = turn_str_to_date(date_str=str1)
                report_date = turn_str_to_date(date_str=t['report_date'])
                differ_day = str_to_date - report_date
                days = differ_day.days
                return days
            elif t['quarter'][-1] == '3':
                str1 = t['quarter'][0:4] + '0930'
                str_to_date = turn_str_to_date(date_str=str1)
                report_date = turn_str_to_date(date_str=t['report_date'])
                differ_day = str_to_date - report_date
                days = differ_day.days
                return days
            elif t['quarter'][-1] == '4':
                str1 = t['quarter'][0:4] + '1231'
                str_to_date = turn_str_to_date(date_str=str1)
                report_date = turn_str_to_date(date_str=t['report_date'])
                differ_day = str_to_date - report_date
                days = differ_day.days
                return days
            else:
                # str1=np.NaN
                return np.NaN

        # forecast_df['AheadDays']=forecast_df.apply(AheadDays,axis=1)

        forecast_df.to_csv('./临时测试数据/预测数据.csv', encoding='utf_8_sig')

        def forecast_statistics(ts_code, quarter):
            import numpy as np
            df = forecast_df[(forecast_df['ts_code'] == ts_code) & (forecast_df['quarter'] == quarter)]
            # print(df)
            if df['ts_code'].count() == 0:
                forecast_num = np.NaN
            else:
                forecast_num = df['ts_code'].count()

            op_rt = round(df['op_rt'].mean(), 0)
            op_rt_std = round(df['op_rt'].std(), 0)

            op_pr = round(df['op_pr'].mean(), 0)
            op_pr_std = round(df['op_pr'].std(), 0)

            tp = round(df['tp'].mean(), 0)
            tp_std = round(df['tp'].std(), 0)

            np = round(df['np'].mean(), 0)
            np_std = round(df['np'].std(), 0)

            eps = round(df['eps'].mean(), 2)
            eps_std = round(df['eps'].std(), 2)

            pe = round(df['pe'].mean(), 2)
            pe_std = round(df['pe'].std(), 2)

            rd = round(df['rd'].mean(), 2)
            rd_std = round(df['rd'].std(), 2)

            roe = round(df['roe'].mean(), 2)
            roe_std = round(df['roe'].std(), 2)

            ev_ebitda = round(df['ev_ebitda'].mean(), 2)
            ev_ebitda_std = round(df['ev_ebitda'].std(), 2)

            max_price = round(df['max_price'].mean(), 2)
            max_price_std = round(df['max_price'].std(), 2)

            min_price = round(df['min_price'].mean(), 2)
            min_price_std = round(df['min_price'].std(), 2)

            rating_list = [i for i in df['rating'].values]

            # for i in rating_list:
            #     if np.isnan(i):
            #         rating_list.remove(i)
            #     else:
            #         pass

            # rating_list=[]
            # for i in df['rating'].values:
            #     if np.isnan(i):
            #         pass
            #     else:
            #         rating_list.append(i)

            rating_set = set(rating_list)

            rating_dict = {i: rating_list.count(i) for i in rating_set}

            s1 = pd.Series(rating_dict)
            s1.sort_values(ascending=False, inplace=True)
            # result_dict={i:s1[i] for i in s1.index}
            if s1.count() == 0:
                rating_level = ''
                print(rating_level)
            else:
                rating_level = ''
                for i in s1.index:
                    rating_level = rating_level + '{}[{}]'.format(i, s1[i]) + ','
                print(rating_level)

            dict1 = {'ts_code': ts_code,
                     'forecast_num': forecast_num,
                     'rating': rating_level,
                     'op_rt': op_rt,
                     'op_rt_std': op_rt_std,
                     'op_pr': op_pr,
                     'op_pr_std': op_pr_std,
                     'tp': tp,
                     'tp_std': tp_std,
                     'np': np,
                     'np_std': np_std,
                     'eps': eps,
                     'eps_std': eps_std,
                     'pe': pe,
                     'pe_std': pe_std,
                     'rd': rd,
                     'rd_std': rd_std,
                     'roe': roe,
                     'roe_std': roe_std,
                     'ev_ebitda': ev_ebitda,
                     'ev_ebitda_std': ev_ebitda_std,
                     'max_price': max_price,
                     'max_price_std': max_price_std,
                     'min_price': min_price,
                     'min_price_std': min_price_std}
            # print(dict1)
            return dict1

        data = pro.stock_basic(exchange='', list_status='L', fields='ts_code,name')
        statistics_list = []
        for i in data['ts_code'].values:
            dict1 = forecast_statistics(ts_code=i, quarter=Quarter)
            statistics_list.append(dict1)
        result_df = pd.DataFrame(statistics_list)
        print(result_df)

        # 添加实际价格数据
        # 日线行情的开始与结束日期函数
        def get_daily_start_end_date(my_quarter):
            if my_quarter[-1] == '4':
                daily_start = my_quarter[0:4] + '1001'
                daily_end = my_quarter[0:4] + '1231'
                return (daily_start, daily_end)
            elif my_quarter[-1] == '3':
                daily_start = my_quarter[0:4] + '0701'
                daily_end = my_quarter[0:4] + '0930'
                return (daily_start, daily_end)
            elif my_quarter[-1] == '2':
                daily_start = my_quarter[0:4] + '0401'
                daily_end = my_quarter[0:4] + '0630'
                return (daily_start, daily_end)
            elif my_quarter[-1] == '1':
                daily_start = my_quarter[0:4] + '0101'
                daily_end = my_quarter[0:4] + '0331'
                return (daily_start, daily_end)

        # # 添加实际价格数据
        # daily_start_date,daily_end_date=get_daily_start_end_date(my_quarter=Quarter)
        # print(daily_start_date,type(daily_start_date),daily_start_date,type(daily_end_date))
        #
        # actual_max_price_list=[]
        # actual_min_price_list = []
        result_df.index = [i for i in result_df['ts_code'].values]
        # for i in result_df['ts_code'].values:
        #     if np.isnan(result_df.loc[i,'forecast_num']):
        #         print(i, np.NaN, np.NaN)
        #         actual_max_price_list.append(np.NaN)
        #         actual_min_price_list.append(np.NaN)
        #     else:
        #
        #         daily_df = pro.query('daily', ts_code=i, start_date=daily_start_date, end_date=daily_end_date)
        #         # close_price=round(daily_df['close'].mean(),2)
        #         max_price=daily_df['close'].max()
        #         min_price=daily_df['close'].min()
        #         print(i,max_price,min_price)
        #         actual_max_price_list.append(max_price)
        #         actual_min_price_list.append(min_price)
        #
        # result_df['actual_max_price']=actual_max_price_list
        # result_df['actual_min_price']=actual_min_price_list
        # print(result_df)

        # 添加实际预测收入和利润
        # 营业总收入
        _, period = get_daily_start_end_date(my_quarter=Quarter)
        total_revenue_data, _ = Financial.PeriodFinancial(statement_type='income', item='total_revenue', period=period)
        total_revenue_list = []
        print(result_df)
        for i in result_df['ts_code'].values:
            x = result_df.loc[i, 'forecast_num']
            print(i, x)
            if np.isnan(result_df.loc[i, 'forecast_num']):
                total_revenue_list.append(np.NaN)
                print(i, np.NaN)
            else:
                try:
                    total_revenue = total_revenue_data.loc[i, period]
                    total_revenue_list.append(total_revenue)
                    print(i, total_revenue)
                except:
                    total_revenue_list.append(np.NaN)
                    print(i, np.NaN)
        result_df['total_revenue'] = total_revenue_list
        # 归母净利润
        n_income_attr_p_data, _ = Financial.PeriodFinancial(statement_type='income', item='n_income_attr_p',
                                                            period=period)
        n_income_attr_p_list = []
        for i in result_df['ts_code'].values:
            x = result_df.loc[i, 'forecast_num']
            print(i, x)
            if np.isnan(result_df.loc[i, 'forecast_num']):
                n_income_attr_p_list.append(np.NaN)
                print(i, np.NaN)
            else:
                try:
                    n_income_attr_p = n_income_attr_p_data.loc[i, period]
                    n_income_attr_p_list.append(n_income_attr_p)
                    print(i, n_income_attr_p)
                except:
                    n_income_attr_p_list.append(np.NaN)
                    print(i, np.NaN)
        result_df['n_income_attr_p'] = n_income_attr_p_list

        result_df.to_csv('./临时测试数据/预测结果统计.csv', encoding='utf_8_sig')

        read_data = pd.read_csv('./临时测试数据/预测结果统计.csv')
        read_data.index = [i for i in read_data['ts_code'].values]
        print(read_data)

        # 添加修正系数功能,来源于20220101~201231预测结果统计
        dict1 = {'总营收修正系数': [20.85 / 100, 19.88 / 100, 18.63 / 100, 17.50 / 100, 15.93 / 100, 14.05 / 100,
                                    13.43 / 100, 12.77 / 100, 13.08 / 100],
                 '归母净利修正系数': [24.45 / 100, 26.58 / 100, 26.53 / 100, 26.30 / 100, 26.63 / 100, 24.44 / 100,
                                      23.36 / 100, 20.35 / 100, 18.23 / 100]}
        correction_factor_df = pd.DataFrame(data=dict1, index=['1', '5', '10', '20', '30', '50', '75', '100', '125'])
        print(correction_factor_df)
        '''
            以上数据来源于自己的研究结论,见资料:
            https://www.aliyundrive.com/s/n9F6Dj7Fjn9
            https://share.weiyun.com/fd54Wgcl
        '''
        for i in correction_factor_df.index.values:
            print(i, type(i))

        correct_op_rt_list = []
        correct_np_list = []
        for i in result_df['ts_code'].values:
            # y = result_df.loc[i, 'forecast_num']
            # x = str(int(y * 1))
            #
            # c_op_rt = correction_factor_df.loc[x, '总营收修正系数']
            # c_np = correction_factor_df.loc[x, '归母净利修正系数']
            # print(i, x, type(x),c_op_rt,c_np)

            try:
                y = result_df.loc[i, 'forecast_num']
                global x1688991
                if y >= 1 and y < 5:
                    x1688991 = '1'
                elif y >= 5 and y < 10:
                    x1688991 = '5'
                elif y >= 10 and y < 20:
                    x1688991 = '10'
                elif y >= 20 and y < 30:
                    x1688991 = '20'
                elif y >= 30 and y < 50:
                    x1688991 = '30'
                elif y >= 50 and y < 75:
                    x1688991 = '50'
                elif y >= 75 and y < 100:
                    x1688991 = '75'
                elif y >= 100 and y < 125:
                    x1688991 = '100'
                elif y >= 125:
                    x1688991 = '125'
                x = x1688991
                print(i, x, type(x))
                c_op_rt = correction_factor_df.loc[x, '总营收修正系数']
                c_np = correction_factor_df.loc[x, '归母净利修正系数']

                # vp1 = round(read_data.loc[i, 'op_rt'] * (1 - c_op_rt), 0)  # 修正后的预测总营业收入
                # vp2 = round(read_data.loc[i, 'np'] * (1 - c_np), 0)  # 修正后的预测归母净利润

                vp1 = round(result_df.loc[i, 'op_rt'] * (1 - c_op_rt), 0)  # 修正后的预测总营业收入
                vp2 = round(result_df.loc[i, 'np'] * (1 - c_np), 0)  # 修正后的预测归母净利润

                correct_op_rt_list.append(vp1)
                correct_np_list.append(vp2)
            except:
                print(i, np.NaN, np.NaN)
                correct_op_rt_list.append(np.NaN)
                correct_np_list.append(np.NaN)

        # read_data['correct_op_rt']=correct_op_rt_list
        # read_data['correct_np']=correct_np_list
        #
        # print(read_data)
        # read_data.to_csv('./临时测试数据/abc.csv',encoding='utf_8_sig')

        result_df['correct_op_rt'] = correct_op_rt_list
        result_df['correct_np'] = correct_np_list

        # TODO 经过修正后的预测数据可能偏小,但可以防止机构高估的问题,此结论有待明日再仔细研究!

        print(result_df)

        # 添加申万行业数
        sw_data = BasicData.Get_Whole_SW()
        sw_data.index = [i for i in sw_data['ts_code'].values]
        is_in_sw_list = []
        for i in sw_data['ts_code'].values:
            if np.isnan(result_df.loc[i, 'forecast_num']):
                is_in_sw_list.append(0)
            else:
                is_in_sw_list.append(1)
        sw_data['is_in_sw'] = is_in_sw_list
        print(sw_data)
        group_data = sw_data.groupby(by=['industry_name1', 'industry_name2', 'industry_name3'])
        total_sw_num = group_data['ts_code'].count()
        forecast_sw_num = group_data['is_in_sw'].sum()
        sw_group_num = pd.DataFrame({'total_sw_num': total_sw_num,
                                     'forecast_sw_num': forecast_sw_num})
        print(sw_group_num)
        name3_list = []
        for i in sw_group_num.index.values:
            _, _, name3 = i
            name3_list.append(name3)

        sw_group_num.index = name3_list
        print(sw_group_num)
        symbol_sw_num_list = []
        print(symbol_sw_num_list)
        for i in result_df['ts_code'].values:
            if np.isnan(result_df.loc[i, 'forecast_num']):
                symbol_sw_num_list.append(np.NaN)
            else:
                sw_name_l3 = sw_data.loc[i, 'industry_name3']
                print(sw_name_l3)
                h = sw_group_num.loc[sw_name_l3, 'forecast_sw_num']
                l = sw_group_num.loc[sw_name_l3, 'total_sw_num']
                symbol_sw_num_list.append('[{}/{}]'.format(h, l))

            # try:
            #     sw_name_l3=sw_data.loc[i,'industry_name3']
            #     print(sw_name_l3)
            #     h=sw_group_num.loc[sw_name_l3,'forecast_sw_num']
            #     l=sw_group_num.loc[sw_name_l3,'total_sw_num']
            #     symbol_sw_num_list.append('[{}/{}]'.format(h,l))
            # except:
            #     symbol_sw_num_list.append(np.NaN)

        result_df['sw_num'] = symbol_sw_num_list
        print(result_df)

        result_df.to_csv('./临时测试数据/abc.csv', encoding='utf_8_sig')
        sw_data.to_csv('./临时测试数据/efg.csv', encoding='utf_8_sig')
        sw_group_num.to_csv('./临时测试数据/uvw.csv', encoding='utf_8_sig')

        '''
        研究结论:
            1.价格预测没有实际意义。准确率大概1/8,没有实际意义。
            2.营业总收入预测结果统计
               筛选总数       20%误差筛选      15%误差筛选     20%正确比率     15%正确率       样本
                2954            2027           1741           68.6%        58.9%        全部有预测数据的股票
                888             695             615            78.3%        69.3%       预测次数大于30的股票
                ... 
                TODO 其他 筛选次数的也要统计一下准确率
            3.归母净利润预测结果统计
               筛选总数       20%误差筛选      15%误差筛选     20%正确比率     15%正确率       样本
                2954            1114            923         37.7%           30.8%       全部有预测数据的股票
                888             435             356         49.0%           40.1%       预测次数大于30的股票
        '''


class Immaturity():
    @classmethod
    def THS_Industry(cls,type):
        # 获取同花顺行业分类数据
        df = pro.ths_index(exchange='A', type=type)
        print(df)
        # 获取申万行业分类数据
        sw_df = BasicData.Read_SW_Industry()
        print(sw_df)
        # 验证同花顺行业指数中的行业是否全部在sw分类中
        name_list = [i for i in df['name'].values]
        in_sw = []
        for i in name_list:
            if i in sw_df['industry_name3'].values:
                in_sw.append('lv3')
            elif i in sw_df['industry_name2'].values:
                in_sw.append('lv2')
            elif i in sw_df['industry_name1'].values:
                in_sw.append('lv1')
            else:
                in_sw.append(np.NaN)
        df['in_sw'] = in_sw
        print(df)
        df.to_csv('./临时测试数据/同花顺行业在SW行业中的级别.csv', encoding='utf_8_sig')
        # 获取同花顺行业指数成分股,在同花顺中,可能出现一个股票同时出现在两个行业中的情况
        member_df = pd.DataFrame()
        for i in range(df['ts_code'].count()):
            print(i)
            df1 = pro.ths_member(ts_code=df['ts_code'].values[i])
            member_df = pd.concat([member_df, df1])
        member_df.index = [i for i in range(member_df['code'].count())]
        print(member_df)
        member_df.to_csv('./临时测试数据/同花顺行业成分.csv', encoding='utf_8_sig')
    @classmethod
    def Investigate(cls,ts_code, start_date, end_date):
        # 定义将日期字符串转化为日期的函数
        def turn_str_to_date(date_str):
            y_int = eval(date_str[0:4])
            m_int = eval(date_str[4:6].strip('0'))
            d_int = eval(date_str[6:8].strip('0'))

            turn_date = datetime.date(year=y_int, month=m_int, day=d_int)

            return turn_date

        # 使用定义的内部函数
        start = turn_str_to_date(date_str=start_date)
        end = turn_str_to_date(date_str=end_date)
        # 创建日期列表
        date_list = []
        mydate = start
        date_list.append(start_date)
        for i in range(12500):
            mydate = mydate + datetime.timedelta(days=1)
            if mydate < end:
                mydate_str = mydate.__str__()
                new_date_str = mydate_str.replace('-', '')
                date_list.append(new_date_str)
            elif mydate == end:
                mydate_str = mydate.__str__()
                new_date_str = mydate_str.replace('-', '')
                date_list.append(new_date_str)
                break
        print(date_list)
        Investigate_DF = pd.DataFrame()
        for i in date_list:
            print(i)
            df = pro.stk_surv(ts_code=ts_code, trade_date=i,
                              fields='ts_code,name,surv_date,fund_visitors,rece_place,rece_mode,rece_org')
            Investigate_DF = pd.concat([Investigate_DF, df])
            time.sleep(0.32)

        Investigate_DF.to_csv('./临时测试数据/机构调研.csv', encoding='utf_8_sig')
    @classmethod
    def IsNewFie(cls,path_suffix, hour, path_prefix='D:\StockAnalysis', identifier=datetime.date.today().__str__()): # 原IsNewFie使用交易日期作为更新标识后,已经比本函数功能更优。保留本函数代码的原因,在于留作以后升级时作为参考
        # 1.将参数中的路径前缀转化为python格式的路径,并前缀电脑中不存在前缀路径,则程序创建一个前缀路径

        prefix = path_prefix.replace('\\', '/')
        prefix_list = prefix.split(sep='/')
        print(prefix_list)
        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)
        print(mypath_prefix)
        # 2.创建路径后缀中的文件夹

        suffix_list1 = path_suffix.split(sep='/')  # 将路径后缀按'/'进行分析,并将分割结果返回成列表数据
        print(suffix_list1)

        lenght = len(suffix_list1)
        suffix_list2 = suffix_list1[1:lenght - 1]  # 列表2中的元素,从前至后,依次为各级文件的名称
        print(suffix_list2)

        mypath = prefix  # 为使用循环反复赋值定义的一个路径变量

        for i in suffix_list2:
            mypath = mypath + '/' + i

            if os.path.exists(mypath):  # 判断文件夹或文件是否存在的函数
                pass
            else:
                os.mkdir(mypath)  # 创建文件夹
        print(mypath)
        # file_path = 'D:/StockAnalysis/BasicData/Save_SW_Stock/申万行业分类(更新日期2023-06-08).csv'
        file_path = mypath + '/' + suffix_list1[-1]
        print(file_path)

        file_timestamp = os.path.getctime(file_path)  # 创建文件的时间戳
        print(file_timestamp)
        turn_tuple = time.localtime(file_timestamp)  # 将时间戳转化成为元组
        print(turn_tuple)

        t1, t2, t3, t4, t5, t6, _, _, _ = turn_tuple

        file_create_time = datetime.time(hour=t4, minute=t5, second=t6)  # 将元组的数据转化为文件创建的时间
        print(file_create_time)

        file_create_date = datetime.date(year=t1, month=t2, day=t3)
        print(file_create_date)

        judge_is_new_time = datetime.time(hour=hour, minute=0, second=0)
        print(judge_is_new_time)
        print(file_create_time > judge_is_new_time)

        date_str = str(t1) + str(t2).rjust(2, '0') + str(t3).rjust(2, '0')
        print(date_str)
        tdy = Today_Date_Count(-1)
        print(tdy)

        # # 获得交易日数据
        # date_df = pro.trade_cal(exchange='', end_date=Today_Date_Count())
        # print(date_df)
        # trade_df=date_df[date_df['is_open']==1]
        # print(trade_df)

        df1 = pro.trade_cal(exchange='SSE', end_date=Today_Date_Count())  # 获取交易日历
        print(df1)
        opendate = df1[df1['is_open'] == 1].reset_index()  # 提取交易日期
        print(opendate)

        cal_date = opendate.loc[:, 'cal_date']  # opendate列索引cal_date中的数据

        cal_date_list = [i for i in cal_date]  # 由上交所交易日期构成的列表

        Now = datetime.datetime.now()  # 当前日期和时间
        TDay = datetime.date.today()  # 当前日期
        Update_time = datetime.datetime(year=TDay.year, month=TDay.month, day=TDay.day, hour=18)  # 日线行情的
        # 增加一个判断,因为周六周日可以任何时间都可获取最近工作日的数据
        WeekDay = TDay.isoweekday()
        if WeekDay > 5:
            recently_trade_date_str = cal_date_list[0]
        else:
            if Now > Update_time:
                # TODO 此判断有一个bug,即周末17:00之前运行得出的数据是周四的,而不是周五的
                recently_trade_date_str = cal_date_list[0]
            else:
                recently_trade_date_str = cal_date_list[1]
        print(recently_trade_date_str, type(recently_trade_date_str))
        y_int = eval(recently_trade_date_str[0:4])
        m_int = eval(recently_trade_date_str[4:6].strip('0'))
        d_int = eval(recently_trade_date_str[6:8].strip('0'))
        print(y_int, m_int, d_int)

        recently_trade_date = datetime.date(year=y_int, month=m_int, day=d_int)
        print(recently_trade_date)
        print(file_create_date)
        print(file_create_date >= recently_trade_date)
        if file_create_date >= recently_trade_date:
            print('已是最新文件')
        else:
            print('正在更新文件......')
    @classmethod
    def Concepts(cls,ts_code): # 同花顺概念函数,由于tushare中无成份股的更新时间参数,因此,此函数无法达到预期功能,固暂时封存于此
        df = pro.ths_index(exchange='A', type='N')
        df.index = [i for i in df['ts_code'].values]
        print(df)
        mem_data = pd.DataFrame()
        for i in df['ts_code'].values:
            print(i)
            df1 = pro.ths_member(ts_code=i)
            mem_data = pd.concat([mem_data, df1])
        print(mem_data)
        mem_data.to_csv('./临时测试数据/同花顺概念成份股.csv', encoding='utf_8_sig')

        bool_data = mem_data[mem_data['code'] == ts_code]
        print(bool_data)
        # t1[t1['X']>30

        Concepts_name_list = []
        for i in bool_data['ts_code']:
            name = df.loc[i, 'name']
            Concepts_name_list.append(name)
        print(Concepts_name_list)
        bool_data['Concepts_name'] = Concepts_name_list
        bool_data.index = [i for i in range(bool_data['ts_code'].count())]
        print(bool_data)
        result_data = bool_data[['code', 'name', 'ts_code', 'Concepts_name']]
        print(result_data)


class Assess():
    @classmethod
    def Update(cls):
        BasicData.Save_SW_Stock() # 运行时间大概三四分钟
        Financial.FinancialItem(statement_type='income',item='total_revenue')
        Financial.FinaIndicator(item='profit_dedt')
        Financial.FinancialItemAnalysis(statement_type='fina_indicator',item='profit_dedt',report_type='年报')
        Price.DailyQuantile(pro_num=500) # 运行时间大概2~3小时
        Price.DailyTargetQuantile(target='pe') # 运行时间大概2小时左右
        Price.DailyTargetQuantile(target='pb')  # 运行时间大概2小时左右
        Price.DailyTargetQuantile(target='ps')  # 运行时间大概2小时左右
    @classmethod
    def ShortTimeUpdate(cls):
        # BasicData.Save_SW_Industry()
        BasicData.Save_SW_Stock()

    @classmethod
    def LongTimeUpdate(cls):

        if __name__ == '__main__':
            '''创建多进程'''
            # 1.日线行情分位数
            Daily_Pro1=multiprocessing.Process(target=Price.DailyQuantile,kwargs={'pro_num':500})
            # 2.每指标分位数
            Daily_Pro2=multiprocessing.Process(target=Upgrade.DailyTargetQuantile,kwargs={'target_list':['pe','pb','ps','turnover_rate_f','volume_ratio','close','total_mv']})

            # 3.判断MA形态
            Daily_Pro3=multiprocessing.Process(target=Technology.MA_Judge,kwargs={'min_num':5,'middle_num':10,'max_num':20,'judge_num':5,'judge_type':'stock'})

            # 4.判断MACD形态
            Daily_Pro4=multiprocessing.Process(target=Technology.MACD_Shape,kwargs={'RecentTradeDayNum':2,'judge_type':'stock'})


            '''运行多进程'''
            Daily_Pro1.start()
            Daily_Pro2.start()
            Daily_Pro3.start()
            Daily_Pro4.start()
    @classmethod
    @classmethod
    def LongTimeUpdateThreading(cls):

        if __name__ == '__main__':
            '''创建多进程'''
            # 1.日线行情分位数
            pro1=threading.Thread(target=Price.DailyQuantile,kwargs={'pro_num':500})

            # 2.每指标分位数
            # Daily_Pro2=multiprocessing.Process(target=Upgrade.DailyTargetQuantile,kwargs={'target_list':['pe','pb','ps','turnover_rate_f','volume_ratio','close','total_mv']})
            pro2=threading.Thread(target=Upgrade.DailyTargetQuantile,kwargs={'target_list':['pe','pb','ps','turnover_rate_f','volume_ratio','close','total_mv']})
            # 3.判断MA形态
            # Daily_Pro3=multiprocessing.Process(target=Technology.MA_Judge,kwargs={'min_num':5,'middle_num':10,'max_num':20,'judge_num':5,'judge_type':'stock'})
            pro3=threading.Thread(target=Technology.MA_Judge,kwargs={'min_num':5,'middle_num':10,'max_num':20,'judge_num':5,'judge_type':'stock'})

            # 4.判断MACD形态
            # Daily_Pro4=multiprocessing.Process(target=Technology.MACD_Shape,kwargs={'RecentTradeDayNum':2,'judge_type':'stock'})
            pro4=threading.Thread(target=Technology.MACD_Shape,kwargs={'RecentTradeDayNum':2,'judge_type':'stock'})


            '''运行多进程'''
            pro1.start()
            pro2.start()
            pro3.start()
            pro4.start()


    @classmethod
    def Timer(cls,hour,minute,sleep): # 定时器函数
        TDay = datetime.date.today()
        Update_time1 = datetime.datetime(year=TDay.year, month=TDay.month, day=TDay.day, hour=hour, minute=minute)  # 日线行情的

        Update_time2 = datetime.datetime(year=TDay.year, month=TDay.month, day=TDay.day, hour=hour+1,
                                         minute=minute)  # 日线行情的
        print(Update_time1)

        while True:
            now = datetime.datetime.now()
            print(now)
            time.sleep(sleep)
            if now > Update_time1 and now<Update_time2:
                print('更新数据')
                # Assess.LongTimeUpdate()
                Assess.LongTimeUpdate()
                break
    @classmethod
    def WanWeiData(cls):
        whole_sw_df=BasicData.Get_Whole_SW()
        print(whole_sw_df)
        wanwei_data=whole_sw_df
        print(wanwei_data)
        # 价格分位数     添加上市年数,今日收盘价,收盘价位置列
        daily_quantile_df=Price.DailyQuantile(pro_num=500)
        print(daily_quantile_df)
        daily_quantile_df.index=[i for i in daily_quantile_df['ts_code'].values]
        v=daily_quantile_df.loc['000001.SZ','position']
        print(v)
        year_list=[daily_quantile_df.loc[i,'year'] for i in wanwei_data['ts_code'].values]
        wanwei_data['year']=year_list
        today_close_list=[daily_quantile_df.loc[i,'today_close'] for i in wanwei_data['ts_code'].values]
        wanwei_data['today_close']=today_close_list
        position_list=[daily_quantile_df.loc[i,'position'] for i in wanwei_data['ts_code'].values]
        wanwei_data['daily_position']=position_list
        # 总营业收入
        total_revenue,_=Financial.FinancialItemAnalysis(statement_type='income',item='total_revenue',report_type='年报')
        print(total_revenue)
        total_revenue_list=[Financial.JudgeFinancialItemGrowth(ts_code=i,data=total_revenue,compare_value=0.2,yoy=0) for i in wanwei_data['ts_code'].values]
        wanwei_data['total_revenue']=total_revenue_list

        # 添加营业收入同比增长情况列
        total_revenue_growth20230621=Financial.RecentlQuarterChange(statement_type='income',item='total_revenue')
        t_YOY_list=[total_revenue_growth20230621.loc[i,'YoY'] for i in wanwei_data['ts_code'].values]
        wanwei_data['t_YOY']=t_YOY_list
        t_sw_growth_list=[total_revenue_growth20230621.loc[i,'sw_lv3_growth'] for i in wanwei_data['ts_code'].values]
        wanwei_data['t_sw_growth']=t_sw_growth_list

        # 扣非净利润
        profit_dedt_df,_=Financial.FinancialItemAnalysis(statement_type='fina_indicator', item='profit_dedt', report_type='年报')
        print(profit_dedt_df)
        profit_dedt_df_list=[Financial.JudgeFinancialItemGrowth(ts_code=i,data=profit_dedt_df,compare_value=0.2,yoy=0) for i in wanwei_data['ts_code'].values]
        wanwei_data['profit_dedt']=profit_dedt_df_list
        print(wanwei_data)

        # 添加归母净利润同比增长情况列
        profit_dedt_df_growth_20230621=Financial.RecentlQuarterChange(statement_type='fina_indicator',item='profit_dedt')
        p_YOY_list=[profit_dedt_df_growth_20230621.loc[i,'YoY'] for i in wanwei_data['ts_code'].values]
        wanwei_data['p_YOY']=p_YOY_list
        p_sw_growth_list=[profit_dedt_df_growth_20230621.loc[i,'sw_lv3_growth'] for i in wanwei_data['ts_code'].values]
        wanwei_data['p_sw_growth']=p_sw_growth_list

        # 添加机构预测营收及利润数据
        forecast_data=Financial.ReadRecentYearForecast()
        forecast_data.index=forecast_data['ts_code'].values
        forecast_num_list=[forecast_data.loc[i,'forecast_num'] for i in wanwei_data['ts_code'].values]
        wanwei_data['forecast_num']=forecast_num_list
        sw_num_list=[forecast_data.loc[i,'sw_num'] for i in wanwei_data['ts_code'].values]
        wanwei_data['sw_num']=sw_num_list
        comp_t2_list = [forecast_data.loc[i, 'comp_t2'] for i in wanwei_data['ts_code'].values]
        wanwei_data['comp_t2'] = comp_t2_list
        comp_n2_list = [forecast_data.loc[i, 'comp_n2'] for i in wanwei_data['ts_code'].values]
        wanwei_data['comp_n2'] = comp_n2_list

        # 优质股列
        high_quality_stock_data = Basics.ReadManualFileData(path_suffix='/ManualEntryFile(NoDelete)/HighQualityStock/优质股.xlsx')

        high_quality_stock_list=[i for i in high_quality_stock_data['代码'].values]
        high_quality_stock_data.index=high_quality_stock_list
        print(high_quality_stock_data)
        high_quality_bool_list=[]
        for i in wanwei_data['ts_code'].values:
            if i in high_quality_stock_list:

                mark=high_quality_stock_data.loc[i,'标记']
                # print(mark)
                high_quality_bool_list.append(mark)
            else:
                mark=np.NaN
                # print(mark)
                high_quality_bool_list.append(mark)

        wanwei_data['superior']=high_quality_bool_list # 添加优质股列





        path='/Assess/WanWeiData/万维分析.csv'
        Basics.PandasSaveData(data=wanwei_data,path_suffix=path)
        '''
        添加列名称解释
        '''
        '''
        第一版20230608:
            分析思路:
                1.历史价格最低,即分位数5%以内,甚至10%以内
                2.营收收入近三年连续增长大于20%
                3.扣非净利润近三年连续增长率大于20%
                4.上市时间不能太短,最好5年以上(暂定标准)
                5.股票价格不能太高,最好在20元以内(暂定标准)
        第二版20230611:
            分析思路:
                1.历史价格最低,即分位数5%以内,甚至10%以内
                2.营收收入近三年连续增长大于20%,且季度同比大于10%(暂定)
                3.扣非净利润近三年连续增长率大于20%,且季度同比大于10%(暂定)
                4.上市时间不能太短,最好5年以上(暂定标准)
                5.股票价格不能太高,最好在20元以内(暂定标准)
                6.属于优质股或符合1,2,3条的股票
            选股逻辑:
                1.1-2-3同时满足
                2.1-6同时满足
        '''

def Update():
    ToDay=datetime.datetime.today()
    # year=ToDay.year
    # month=ToDay.month
    # day=ToDay.day+1
    UseDay=ToDay+datetime.timedelta(days=1)
    year=UseDay.year
    month=UseDay.month
    day=UseDay.day
    '''
    以上代码是2024年3月31日运行出错后完善后的代码。
    '''
    Basics.MyTimer(function=BasicData.Save_SW_Stock,year=year,month=month,day=day,hour=0,minute=0,second=0)
    Basics.MyTimer(function=Assess.LongTimeUpdateThreading,year=year,month=month,day=day,hour=0,minute=15,second=0)
    Basics.MyTimer(function=Assess.WanWeiData,year=year,month=month,day=day,hour=7,minute=30,second=0)
def ShortTermStock():
    pass
    '''
    可以使用行业技术指标(MA,MACD,成交量),行业趋势,热点行业(聚焦涨停功能)进行短线投资,相关分析待日后完善。
    '''

Update()

# df = ts.pro_bar(ts_code='000039.SZ', adj='hfq', start_date='20180101')
#
# df.index=df['trade_date'].values
# print(df)
# print(df.loc['20220513','close'])

# TODO 将市净率及市盈率作为判断条件添加至分析数据中
# TODO 设计分类优选:1.基本面好,股价较低;2.基本好,指标较低;3.龙头或蓝筹,股价较低;4.龙头或蓝筹,指标较低。共性条件:上市8年以上,若用价格选股,股价20元以内/20年以上的股票,可以采用15~20年以内指标较低选择或借壳上市后最低

'''
# TODO 将市净率及市盈率作为判断条件添加至分析数据中
# TODO 设计分类优选:
        1.基本面好,股价较低;
        2.基本好,指标较低;
        3.龙头或蓝筹,股价较低;
        4.龙头或蓝筹,指标较低。
    共性条件:
        1.上市8年以上
        2.若用价格选股,股价20元以内
        3.20年以上的股票,可以采用15~20年以内指标较低选择或借壳上市后最低
    关于市净率的应用设想(20230810得出的设想):
        1.市净率低于1的股票,只要业务不是太烂,都可以考虑持有。市净率处于1以
          下,股价处于低位,总会有反弹的时候,这样的股票,如果反弹,1年内股价
          增幅超过30%应是很轻松的事情。观察去年选择的破净股票中国中车,广深铁
          路,中国重工,首创环保,中油工程,美凯龙,五矿资本,中粮资本,中得出
          的结论。尤其是央企背景的公司,更可以考虑。但是山鹰国际股价一直在下行,
          一年内9个股票只有一个股价下跌,由此可以看出,即便在大环境不好的情况下,
          这种选股方式依然是一个不错的方法。
        2.有的公司市净率低于1,其股价的市净率水平,可以参考行业排名及行业历史市
          盈率水平来确定是否低估。比如行业历史市盈率均值为20,而目前一直股票其市
          盈率为16,行业上市公司数量为20,而其在行业排名为8,则明显可以得出结论
          其市盈率被低估。
        3.采用市净选股,加上等额分散投资的策略,是很有必要的。因为自己目前还没有
          把握准确预测每只股票的涨跌时间。2023年8月23日。
'''
def ComporationForecast():
    df = pro.forecast_vip(period='20230630',fields='ts_code,ann_date,end_date,type,p_change_min,p_change_max,net_profit_min')
    print(df)
    df.to_csv('./临时测试数据/业绩预测.csv',encoding='utf_8_sig')
# ComporationForecast()

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值