深入分析:使用Python和Akshare进行单基金绩效评估与风险收益指标计算

深入分析:使用Python和Akshare进行单基金绩效评估

在金融投资领域,基金绩效评估是一项至关重要的任务。它不仅帮助投资者做出明智的投资决策,还能为基金经理提供业绩反馈。本文将详细介绍如何使用Python编程语言,结合Akshare库和Pandas数据分析工具,对单基金进行全面的绩效评估。

环境搭建

首先,确保你的Python环境中安装了以下库:

  • Akshare:用于获取金融数据。
  • Pandas:用于数据处理和分析。
  • Numpy:用于数值计算。
  • PyMySQL:用于数据库操作。

安装命令如下:

pip install akshare pandas numpy pymysql

数据获取

我们将从Akshare获取沪深300指数的历史数据,作为基金绩效评估的基准。

import akshare as ak

# 获取沪深300指数的历史数据
index_benchmark_code = "000300"
index_data = ak.index_zh_a_hist(symbol=index_benchmark_code, period="daily", 
                               start_date='2023-07-29', end_date='2024-07-29')

数据库连接

使用PyMySQL库连接到MySQL数据库,以获取基金的基本信息和净值数据。

import pymysql
from pymysql.cursors import DictCursor

def mysql_connect(sql):
    conn = pymysql.connect(host="", charset='utf8mb4')
    cursor = conn.cursor(DictCursor)
    cursor.execute(sql)
    res = cursor.fetchall()
    res = pd.DataFrame(res)
    conn.close()
    return res

基金信息加载

编写函数从数据库加载基金的基本信息和净值数据。

def load_fund_info(fund_type):
    query = f"SELECT fund_code FROM public_offering WHERE fund_type = '{fund_type}';"
    funds_list = mysql_connect(query)["fund_code"].tolist()
    return funds_list

def load_fund_nav(fund_code):
    # 根据基金代码从数据库加载净值数据
    # 此处省略具体SQL查询和数据处理细节
    pass

基金绩效评估

实现basic_factor函数,计算基金的各类风险收益指标。

def basic_factor(start_date, end_date, fund_codes, benchmark_code):
    # 根据基金代码和日期范围,计算基金的绩效指标
    # 此处省略具体计算细节
    pass

排名与百分比计算

编写rank_and_percent函数,为基金的每个指标计算排名和百分比排名。

def rank_and_percent(df, column_name):
    df[f"{column_name}_Rank"] = df[column_name].rank(method='max', ascending=False)
    df[f"{column_name}_Percentile"] = (1 - (df[f"{column_name}_Rank"] / (len(df) + 1))) * 100
    return df[[column_name, f"{column_name}_Rank", f"{column_name}_Percentile"]]

引入必要的库

import akshare as ak
import pandas as pd
from datetime import datetime, timedelta
from typing import List
import pymysql
from pymysql.cursors import DictCursor

获取数据

我们将从Akshare获取沪深300指数的历史数据,并使用Pandas进行处理。

index_benchmark_code = "000300"
df_index_day = ak.index_zh_a_hist(symbol=index_benchmark_code, period="daily",
                                 start_date=(end_date - timedelta(days=365)).replace("-", ""),
                                 end_date=end_date.replace("-", ""),
                                 )

单基金分析

接下来,我们将编写函数来分析单基金的表现。这包括计算日收益率、累积收益率、最大回撤、夏普比率等。

import akshare as ak
import pandas as pd

# ********** 单基金分析***********
# 产品名称	产品代码	净值日期	单位净值	累计净值
from datetime import timedelta
import numpy as np
import pandas as pd
from typing import List
import pymysql
from pymysql.cursors import DictCursor

# 获取沪深300指数的历史数据 399006 000001
index_benchmark_code = "000300"
# 单基金分析函数
# 各类指标计算函数
rf_rate = 0.02
from datetime import datetime, timedelta

# 获取今天的日期
today = datetime.today()

# 设置end_date为今天
end_date = today.strftime('%Y-%m-%d')

# 计算start_date为近一周、近一月、近3月、近6月、近一年
start_dates = {
    '近一周': (today - timedelta(days=7)).strftime('%Y-%m-%d'),
    '近一月': (today - timedelta(days=30)).strftime('%Y-%m-%d'),
    '近3月': (today - timedelta(days=90)).strftime('%Y-%m-%d'),
    '近6月': (today - timedelta(days=180)).strftime('%Y-%m-%d'),
    '近一年': (today - timedelta(days=368)).strftime('%Y-%m-%d')
}

print(f"end_date: {end_date}")
for period, start_date in start_dates.items():
    print(f"{period} start_date: {start_date}")
start_date = start_dates['近一年']
# end_date = '2024-07-29'
FundType = '偏股型基金'
reg_codelist = ['000021']
# index = ['000905.SH']
offset = None
alpha: bool = None


def mysql_connect(sql):
    """
    :return: mysql的连接,并处理、返回sql语句
    """
    conn = None
    try:
        conn = pymysql.connect(
            host="",
           
            # charset='utf8'
            charset='utf8mb4'  # 使用utf8mb4以支持更多字符
        )
        cur = conn.cursor(DictCursor)
        cur.execute(sql)
        res = cur.fetchall()
        res = pd.DataFrame(res)
    except pymysql.err.OperationalError as e:
        print("数据库连接失败:", e)
    finally:
        # 确保即使发生异常也关闭连接
        if conn:
            conn.close()
            print("MySQL连接已关闭。")
    return res


# 示例函数框架,具体实现需要根据数据库结构和数据访问方式进行调整

def load_HF_basci_info(Fundtype):
    # fund_data = pd.read_excel("fund1.xlsx").head(241)
    # 这里应该是从数据库加载私募基金的基本信息
    # 假设返回一个DataFrame
    query = "SELECT fund_code FROM public_offering WHERE fund_type = '{}';".format(Fundtype)
    fundsList = list(mysql_connect(query)["fund_code"])
    return fundsList


def load_HF_nav(productCode):
    query_dite = """SELECT equity_date,product_code,product_name,unit_net_worth,cumulative_net_worth FROM public_offering_worth WHERE product_code = {} AND equity_date BETWEEN DATE('{}') AND DATE('{}');""".format(
        productCode, start_date, end_date)
    df = mysql_connect(query_dite)
    df['cumulative_nav'] = df['cumulative_net_worth']
    df['price_date'] = df['equity_date']
    df["fundsname"] = df["product_code"]
    # df['price_date'] = df['单位净值'].cumprod()
    return df


def load_Trade_calendar(start_date, end_date):
    # 创建日期范围
    date_range = pd.date_range(start=start_date, end=end_date)

    # 创建DataFrame
    trade_cal_day = pd.DataFrame({
        'trading_date': date_range
    })

    # 确保交易日是工作日,排除周末(假设交易日为周一至周五)
    trade_cal_day = trade_cal_day[~trade_cal_day['trading_date'].dt.weekday.isin([5, 6])]

    # 将'trading_date'列转换为Datetime类型
    trade_cal_day['trading_date'] = pd.to_datetime(trade_cal_day['trading_date'])

    # 将'trading_date'设置为索引
    trade_cal_day.set_index('trading_date', inplace=True)

    # 如果需要,可以添加其他相关列,例如是否为周频的最后一个交易日
    # trade_cal_day['is_week_last_trading_day'] = trade_cal_day.index.isoweekday == 5

    return trade_cal_day


def load_Index_price(index: List[str], start_date: str, end_date: str, ) -> pd.DataFrame:
    # 这里应该是从数据库加载指数的行情数据
    # 假设返回一个DataFrame,包含'trade_date'和'close'列

    sz_index_df = ak.index_zh_a_hist(symbol=index_benchmark_code, period="daily",
                                     start_date=start_date.replace("-", ""),
                                     end_date=end_date.replace("-", ""),
                                     # adjust="qfq"
                                     )
    return sz_index_df


# 定义业绩指标计算函数
def basic_factor(start_date: str, end_date: str, reg_codelist: List[str], index: str, offset: int = None,
                 alpha: bool = False):
    """
    计算单一私募基金的基本风险收益指标,返回风险收益指标的字典,以及整理完完毕的数据

    可以指定窗口期,计算当前最新净值日期前推offset期间的各类风险收益指标

    返回数据中包括交易日、指数行情信息、私募基金净值(已经归一化),作为绘图的基础

    根据单个基金进行分析;多个基金需要把结果整合在一起。
    设置一个开关,可以导出单基金分析的结果excel,可以多个基金一起生成excel,循环调用这个函数
    在绘图的时候可以是静态图也可以是动态图


    Parameters
    ----------
    start_date:str
        开始日期,格式'YYYY-mm-dd'

    end_date:str
        结束日期,格式'YYYY-mm-dd'

    offset:int
        窗口期,如果传入则计算对应窗口的风险收益指标,如果不传入,则计算选择的start-end样本期的指标,默认计算所选样本期

    reg_codelist:List[str]
        基金的代码,对应数据库中的reg_code,格式为['reg_codelist']

    index:List[str]
        指数基准,对应数据库index_price中的指数基准

    alpha:bool = False
        是否是指数增强基金,指数增强基金会计算超额指标

    """
    # 从数据库加载基金的基本信息
    df_fund_info = load_HF_basci_info(FundType)

    # 从数据库加载基金净值数据,按照交易日索引排序,补充一列pre_cumulative_nav
    # fundsname=df_fund_info[0]
    dataList = []
    for fundsname in df_fund_info[:20]:
        print(fundsname)
        df_one = load_HF_nav(fundsname)
        df_one = df_one.sort_values("price_date")
        df_one.index = list(range(len(df_one)))
        df_one['price_date'] = pd.to_datetime(df_one['price_date'])
        df_one.set_index('price_date', inplace=True)

        df_one = df_one[start_date:end_date]
        df_one['fundsname'] = fundsname  # df_fund_info.fundsname.values[0]
        # df_one = df_one.reset_index()
        # try:
        df_one['unit_net_worth'] = df_one['unit_net_worth'].astype("float")
        df_one['cumulative_net_worth'] = df_one['cumulative_net_worth'].astype("float")
        df_one['cumulative_nav'] = df_one['cumulative_nav'].astype("float")
        df_one['pre_cumulative_nav'] = df_one['cumulative_nav'].shift(1)

        # 存在一种情况,开始和结束日期的参数区间大于实际基金的净值开始日期,此时应该将开始日期转为基金净值的实际开始日期
        intervaldate = 0
        # if df_one.index[0].strftime('%Y-%m-%d') >= start_date:
        #     start_date = df_one.index[-1].strftime('%Y-%m-%d')
        #     intervaldate = 1  # 表明所选择参数区间的起始日已经小于了基金的成立日

        # 从数据库加载交易日历,按照交易日索引排序
        # trade_cal_day = load_Trade_calendar(start_date, end_date)
        # trade_cal_day=trade_cal_day.reset_index()
        trade_cal_day = pd.DataFrame()
        trade_cal_day["trading_date"] = df_one.reset_index()['price_date']
        trade_cal_day = trade_cal_day[(trade_cal_day['trading_date'] >= start_date) & (
                trade_cal_day['trading_date'] <= end_date)]
        trade_cal_day['trading_date'] = pd.to_datetime(
            trade_cal_day['trading_date'])
        # trade_cal_day.set_index('trading_date', inplace=True)
        # 周频交易日历,每周最后一个交易日
        trade_cal_day['if_week_end'] = trade_cal_day['trading_date'].dt.weekday == 4  # 4代表星期五

        # 将布尔值转换为'是'和'否'
        trade_cal_day['if_week_end'] = trade_cal_day['if_week_end'].map({True: '是', False: '否'})
        trade_cal_week = trade_cal_day[trade_cal_day['if_week_end'] == '是']

        # # 从数据库加载指数行情数据,按照交易日索引排序,指数周频行情和日频行情分开处理,确保pre_close的准确性
        df_index_day = load_Index_price(index=index, start_date=start_date, end_date=end_date)
        df_index_day['trade_date'] = df_index_day['日期']
        df_index_day['close'] = df_index_day['收盘']
        # df_index_day = df_index_day[(df_index_day['trade_date'] >= start_date.replace(
        #     '-', '')) & (df_index_day['trade_date'] <= end_date.replace('-', ''))]
        df_index_day['trade_date'] = pd.to_datetime(df_index_day['trade_date'])
        df_index_day.set_index('trade_date', inplace=True)


        """
        净值序列的几种可能情况:
        1、完整的日净值
        2、完整的周净值
        3、部分时间为日净值,部分时间为周净值
        4、净值缺失严重,区间内净值不完整,有较长的时间段无净值,或区间开始有净值,后续不更新净值
        5、假设周净值均是每周的最后一个交易日(不一定是最后一个自然日)发布,一些异常情况净值在每周的周中发布
        """
        # 针对1-4的情况解决方案
        trade_cal_day.index = trade_cal_day["trading_date"]

        nav_day_cal_df = pd.merge(trade_cal_day, df_one,
                                  left_index=True, right_index=True, how='left')  # 合并日频交易日和净值数据
        nav_week_cal_df = pd.merge(trade_cal_week, df_one,
                                   left_index=True, right_index=True, how='left')  # 合并周频交易日和净值数据
        nav_outer_df = pd.merge(trade_cal_week, df_one,
                                left_index=True, right_index=True, how='outer')  # 合并周频交易日和净值数据,合并方式'outer'
        nav_outer_df.fillna(method='ffill', limit=1, inplace=True)

        # 计算日或周净值的完整度
        # nav_frequceny_day = 1-nav_day_cal_df.nav.isnull().sum()/len(nav_day_cal_df)
        # nav_frequceny_week = 1-nav_week_cal_df.nav.isnull().sum()/len(nav_week_cal_df)
        # nav_frequceny_outer = 1-nav_outer_df.nav.isnull().sum()/len(nav_outer_df)
        #
        nav_frequency = 2
        # if (nav_frequceny_day >= 0.90) & (nav_frequceny_week >= 0.9):  # 原始净值为比较完美的日净值
        #     nav_frequency = 2  # 日净值
        # elif (nav_frequceny_week >= 0.90) & (nav_frequceny_day < 0.90):  # 原始净值为比较完美的周净值
        #     nav_frequency = 1   # 周净值
        # elif (nav_frequceny_week < 0.90) & (nav_frequceny_day < 0.90) & (nav_frequceny_outer > 0.90):  # 原始净值为比较完美的周中发布的周净值
        #     nav_frequency = 3   # 周中发布的周净值
        # else:
        #     print('{}产品的净值严重缺失,无法计算:'.format(df_fund_info.fundsname.values[0]))
        #     return   # 如果净值缺失严重停止后续计算

        # 根据净值频率,合并交易日、净值数据、指数行情数据、计算净值以及指数的涨跌幅序列
        # 周净值
        # if nav_frequency == 1:
        #     nav_one = nav_week_cal_df
        #     # nav_one = pd.merge(nav_one, df_index_week, left_index=True,
        #     #                    right_index=True, how='left')
        #     nav_one = pd.merge(nav_one, left_index=True,
        #                        right_index=True, how='left')

        # 日净值
        # if nav_frequency == 2:
        # nav_one = nav_day_cal_df
        nav_one = pd.merge(df_index_day, nav_day_cal_df, left_index=True,
                           right_index=True, how='left')
        nav_one.fillna(method='ffill', inplace=True)
        # nav_one = pd.merge(nav_one, left_index=True,
        #                    right_index=True, how='left')

        # # 如果是周中发布的周净值
        # if nav_frequency == 3:  # 周中发布净值的修正
        #     nav_one = nav_outer_df
        #     # nav_one = pd.merge(nav_one, df_index_week, left_index=True,
        #     #                    right_index=True, how='right')
        #     nav_one = pd.merge(nav_one, left_index=True,
        #                        right_index=True, how='right')
        #     nav_frequency = 1  # 修正完成后与一般的周净值无异

        # # 剔除无用字段
        # nav_one.drop(['if_trading_day', 'if_week_end',
        #               'if_month_end', 'price_change', 'pct_chg'], axis=1, inplace=True)

        # nav_one.fillna(method='ffill', inplace=True)

        # 如果参数区间完全覆盖了产品成立起始日,此时可能出现周净值或者日净值期初为缺失值的情况,将期初情况填充为1
        # if intervaldate == 0:
        # nav_one.nav.fillna(1, inplace=True)
        # nav_one.cumulative_nav_withdrawal.fillna(1, inplace=True)
        # nav_one.cumulative_nav.fillna(1, inplace=True)
        # nav_one.pre_cumulative_nav.fillna(1, inplace=True)
        # nav_one.reg_code.fillna(reg_codelist[0], inplace=True)
        # nav_one.fundsname.fillna(
        #     df_fund_info.fundsname.values[0], inplace=True)

        # 计算复权累计净值的周度或者日度涨跌幅
        # 日或周复权净值涨跌幅
        # nav_one['cumulative_nav']=nav_one['cumulative_nav'].astype("float")
        nav_one['cumulative_nav'] = nav_one['cumulative_nav'].astype("float")
        # nav_one.cumulative_nav=nav_one.cumulative_nav.fillna(method='ffill').fillna(method='bfill')
        # nav_one.pre_cumulative_nav=nav_one.pre_cumulative_nav.fillna(method='ffill').fillna(method='bfill')

        nav_one['cumulative_nav_pct'] = nav_one['cumulative_nav'].pct_change()

        # nav_one=nav_one.reset_index()

        # nav_one.cumulative_nav_pct=nav_one.cumulative_nav_pct.fillna(method='ffill').fillna(method='bfill')
        # nav_one['cumulative_nav_pct'].iloc[0] = nav_one['cumulative_nav'].iloc[0] / \
        #     nav_one['pre_cumulative_nav'].iloc[0]-1

        nav_one['close_pct'] = nav_one['close'].pct_change()  # 日或周指数行情涨跌幅
        nav_one['pre_close'] = nav_one['close'].shift(1)
        nav_one = nav_one.drop(index=start_date)
        start_date = str(nav_one.index[0]).split(" ")[0]

        # nav_one['pre_close']=nav_one['pre_close'].fillna(method='ffill').fillna(method='bfill')
        # nav_one['close_pct'].iloc[0] = nav_one['close'].iloc[0] / \
        #     nav_one['pre_close'].iloc[0]-1

        # *** 至此净值已处理干净 ***

        # 是否计算窗口期,如果不计算窗口期,则以全样本为分析对象
        if offset:
            offset = timedelta(days=offset)
            date_start = nav_one.index[-1].date() - offset
        else:
            date_start = nav_one.index[0]

        date_end = nav_one.index[-1]  # 分析样本的截止日期

        # 最终需要计算的样本对象
        nav_df_part = nav_one[date_start: date_end]
        adj_nav_end = nav_df_part['cumulative_nav'].iloc[-1]  # 复权累计净值的区间末位数值
        adj_nav_start = nav_df_part['cumulative_nav'].iloc[0]  # 复权累计净值的区间首位数值
        nav_shift1_start = nav_df_part['pre_cumulative_nav'].iloc[0]

        # 复权累计净值归一、指数收盘价归一,待后续使用
        nav_df_part['nav_unit'] = nav_df_part['cumulative_nav'] / adj_nav_start

        nav_df_part['close_unit'] = nav_df_part['close'] / \
                                    nav_df_part['close'].iloc[0]

        # 样本期的绝对收益率
        abs_ret = adj_nav_end / adj_nav_start - 1

        # 样本期的年化收益率
        if nav_frequency == 1:  # 周净值
            annual_ret = pow(adj_nav_end / adj_nav_start, 52 / (len(nav_df_part) - 1)) - 1
        if nav_frequency == 2:
            annual_ret = pow(adj_nav_end / adj_nav_start, 252 / (len(nav_df_part) - 1)) - 1

        # 样本期的最大回撤
        interval_max_down = ((nav_df_part['cumulative_nav'].cummax() - nav_df_part['cumulative_nav']) /
                             (nav_df_part['cumulative_nav'].cummax())).max()

        # 样本期年化波动率
        if nav_frequency == 1:
            annual_var = nav_df_part['cumulative_nav_pct'].std(ddof=1) * pow(52, 0.5)
        if nav_frequency == 2:
            annual_var = nav_df_part['cumulative_nav_pct'].std(
                ddof=1) * pow(252, 0.5)

        # 样本期间年化夏普,年化后的平均收益率-无风险利率 /年化后的波动率
        if nav_frequency == 1:
            annual_sharpe = (
                                    pow((1 + nav_df_part['cumulative_nav_pct'].mean()), 52) - 1 - rf_rate) / annual_var
        if nav_frequency == 2:
            annual_sharpe = (
                                    pow((1 + nav_df_part['cumulative_nav_pct'].mean()), 252) - 1 - rf_rate) / annual_var

        # 样本期卡玛比率
        interval_calmar = annual_ret / interval_max_down

        # 样本期下行波动率
        if nav_frequency == 1:
            temp = nav_df_part[nav_df_part['cumulative_nav_pct']
                               < nav_df_part['cumulative_nav_pct'].mean()]
            temp2 = temp['cumulative_nav_pct'] - \
                    nav_df_part['cumulative_nav_pct'].mean()
            down_var = np.sqrt((temp2 ** 2).sum() / (len(nav_df_part) - 1)) * pow(52, 0.5)
        if nav_frequency == 2:
            temp = nav_df_part[nav_df_part['cumulative_nav_pct']
                               < nav_df_part['cumulative_nav_pct'].mean()]
            temp2 = temp['cumulative_nav_pct'] - \
                    nav_df_part['cumulative_nav_pct'].mean()
            down_var = np.sqrt((temp2 ** 2).sum() / (len(nav_df_part) - 1)) * pow(252, 0.5)

        # 假设df是一个DataFrame,包含基金和市场的回报率列
        # 列名分别为'fund_returns'和'market_returns'
        # df = pd.DataFrame({
        #     'fund_returns': nav_one['cumulative_nav_pct'] * 100,  # 随机生成基金回报率数据
        #     'market_returns': nav_one['涨跌幅']  # np.random.normal(0, 1, 100)  # 随机生成市场回报率数据
        # })

        # 假设stock_returns和market_returns是Pandas Series,包含股票和市场指数的收益率
        # 这里使用随机数据作为示例
        # np.random.seed(42)  # 为了可重复性设置随机种子
        # stock_returns = np.random.normal(loc=0, scale=1, size=100)  # 股票收益率
        # market_returns = np.random.normal(loc=0, scale=1.5, size=100)  # 市场指数收益率

        # 将收益率转换为Pandas Series
        stock_returns = pd.Series(nav_one['cumulative_nav_pct'] * 100) # 股票收益率
        market_returns = pd.Series(nav_one['涨跌幅']) # 市场指数收益率

        # 计算Beta系数
        # 计算协方差
        covariance = stock_returns.cov(market_returns)
        # 计算市场方差
        market_variance = market_returns.var()
        # Beta系数是协方差除以市场方差
        beta = 0.0018#covariance / market_variance

        # 使用函数计算Beta系数
        print(f"Beta系数: {beta}")

        # # 计算协方差
        # covariance = np.cov(df['fund_returns'], df['market_returns'])[0][1]
        #
        # # 计算市场回报率的方差
        # market_variance = np.var(df['market_returns'])

        # 计算Beta系数
        # Beta大于1:基金的波动性大于市场,风险和潜在回报可能更高。
        # Beta小于1:基金的波动性小于市场,风险和潜在回报可能更低。
        # Beta等于1:基金的波动性与市场一致。
        beta = covariance / market_variance

        print(f"Beta系数: {beta}")
        # market_returns = df['market_returns']
        expected_return = beta * market_returns.mean()
        jensen_alpha = nav_df_part['cumulative_nav_pct'].mean() - expected_return

        # # 计算索提诺比率
        # downside_returns = nav_df_part[nav_df_part['cumulative_nav_pct'] < 0]
        # downside_returns = nav_df_part[nav_df_part['cumulative_nav_pct'] <= 0]['cumulative_nav_pct']
        # sortino_ratio = annual_ret / downside_returns.std(ddof=1) * np.sqrt(nav_frequency)
        # 假设benchmark_return是基准组合的收益率
        # 计算下行波动率
        downside_returns = nav_df_part[nav_df_part['cumulative_nav_pct'] <= 0]['cumulative_nav_pct']
        downside_std = downside_returns.std(ddof=1)

        # 年化下行波动率
        annualized_downside_std = downside_std * np.sqrt(252)

        # 计算索提诺比率
        sortino_ratio = annual_ret / annualized_downside_std

        # # # # 计算年化收益率
        annualized_return =annual_ret
        # sortino_ratio1 = annualized_return / nav_df_part['cumulative_nav_pct'].std() * np.sqrt(365)
        #
        # 计算年化波动率
        annualized_volatility = annual_var

        # 计算夏普比率
        # sharpe_ratio = (annualized_return - rf_rate) / annualized_volatility

        # beta = 2
        # 计算特雷诺指数
        treynor_ratio = (annualized_return - rf_rate) / beta

        # 计算詹森指数
        # jensen_alpha = annualized_return - (rf_rate + beta * (market_returns.mean() - rf_rate))

        # 计算M2测度
        # m2_measure = annualized_return - (rf_rate + beta * market_returns.mean())
        m2_measure = annual_ret - (rf_rate + beta * (market_returns.mean() - rf_rate))

        # 假设我们有基金的回报率和基准的回报率
        fund_returns = np.array(nav_one['cumulative_nav_pct'] * 100)
        benchmark_returns = np.array(nav_one['涨跌幅'])

        # 计算超额回报
        excess_returns = fund_returns - benchmark_returns

        # 计算跟踪误差
        # tracking_error = np.sqrt(np.mean((excess_returns - np.mean(excess_returns)) ** 2))
        # 计算信息比率
        # 假设benchmark_return是基准组合的收益率

        # 假设df_index_day是包含沪深300指数日收盘价格的DataFrame
        # 计算日收益率
        df_index_day['daily_return'] = df_index_day['close'].pct_change()

        # 计算累积收益率
        df_index_day['cumulative_nav'] = (1 + df_index_day['daily_return']).cumprod() - 1

        # 查看累积收益率
        benchmark_return=df_index_day['cumulative_nav'].mean()

        excess_returns = nav_df_part['cumulative_nav_pct'] - benchmark_return
        information_ratio = excess_returns.mean() / excess_returns.std(ddof=1)
        # information_ratio = m2_measure / tracking_error  # tracking_error是基金相对于基准的跟踪误差



        def calculate_semivariance(returns, target_return=0):
            """
            计算半方差
            :param returns: 投资组合的收益率序列,Pandas Series类型
            :param target_return: 目标收益率,默认为0
            :return: 半方差
            """
            # 计算收益低于目标收益的情况
            negative_deviation = returns[returns < target_return]
            # 计算负偏差的平方
            squared_negative_deviation = negative_deviation ** 2
            # 计算平均值
            mean_squared_negative_deviation = squared_negative_deviation.mean()
            # 半方差是这个平均值的平方根
            semivar = mean_squared_negative_deviation ** 0.5
            return semivar

        # 计算Stutzer指数
        # 示例:假设我们有一个投资组合的月收益率序列
        returns = nav_one['涨跌幅']  # pd.Series([0.02, -0.01, 0.03, -0.02, 0.01, -0.03, 0.02, 0.01, -0.02, 0.03])

        # 计算半方差
        semivariance = calculate_semivariance(returns)
        stutzer_index = (annualized_return - rf_rate) / (annualized_volatility + semivariance)

        # {'索提诺比率': sortino_ratio, '特雷诺指数': treynor_ratio, '詹森指数': jensen_alpha, 'M2测度': m2_measure, '信息比率': information_ratio,"Stutzer指数":stutzer_index
        # }

        # 结果字典
        basic_factor_dict = {'abs_ret': abs_ret, 'annual_ret': annual_ret,
                             'interval_max_down': interval_max_down, 'annual_var': annual_var,
                             'annual_sharpe': annual_sharpe, 'interval_calmar': interval_calmar,
                             'down_var': down_var}

        # 如果是指数增强基金,计算超额净值以及超额净值相关风险收益指标
        if alpha:
            nav_df_part['alpha_unit'] = 1 + \
                                        (nav_df_part['nav_unit'] - nav_df_part['close_unit'])
            nav_df_part['alpha_per_day'] = nav_df_part['cumulative_nav_pct'] - \
                                           nav_df_part['close_pct']

            # 样本期累计超额收益率

            cum_alpha_ret = (
                                    nav_df_part['alpha_unit'].iloc[-1] / nav_df_part['alpha_unit'].iloc[0]) - 1

            # 样本期年化超额收益

            if nav_frequency == 1:
                annual_alpha_ret = pow(
                    nav_df_part['alpha_unit'].iloc[-1] / nav_df_part['alpha_unit'].iloc[0],
                    52 / (len(nav_df_part) - 1)) - 1
            if nav_frequency == 2:
                annual_alpha_ret = pow(
                    nav_df_part['alpha_unit'].iloc[-1] / nav_df_part['alpha_unit'].iloc[0],
                    252 / (len(nav_df_part) - 1)) - 1

            # 样本期超额最大回撤
            alpha_max_down = ((nav_df_part['alpha_unit'].cummax() - nav_df_part['alpha_unit']) /
                              (nav_df_part['alpha_unit'].cummax())).max()

            # 样本期超额年化波动率
            if nav_frequency == 1:
                annual_alpha_var = nav_df_part['alpha_per_day'].std(
                    ddof=1) * pow(52, 0.5)
            if nav_frequency == 2:
                annual_alpha_var = nav_df_part['alpha_per_day'].std(
                    ddof=1) * pow(252, 0.5)

            # 样本期间超额夏普

            annual_alpha_sharpe = (annual_alpha_ret - rf_rate) / annual_alpha_var

            basic_factor_dict = {'abs_ret': abs_ret, 'annual_ret': annual_ret,
                                 'interval_max_down': interval_max_down, 'annual_var': annual_var,
                                 'annual_sharpe': annual_sharpe, 'interval_calmar': interval_calmar,
                                 'down_var': down_var, 'cum_alpha_ret': cum_alpha_ret,
                                 'annual_alpha_ret': annual_alpha_ret, 'alpha_max_down': alpha_max_down,
                                 'annual_alpha_var': annual_alpha_var, 'annual_alpha_sharpe': annual_alpha_sharpe
                                 }
            basic_factor_dict_chinse = {'样本期的绝对收益率': abs_ret, '样本期的年化收益率': annual_ret,
                                        '样本期的最大回撤': interval_max_down, '样本期年化波动率': annual_var,
                                        '样本期间年化夏普': annual_sharpe, '样本期卡玛比率': interval_calmar,
                                        '样本期下行波动率': down_var, '样本期累计超额收益率': cum_alpha_ret,
                                        '样本期年化超额收益': annual_alpha_ret, '样本期超额最大回撤': alpha_max_down,
                                        '样本期超额年化波动率': annual_alpha_var,
                                        '样本期间超额夏普': annual_alpha_sharpe, '索提诺比率': sortino_ratio,#'索提诺比率1': sortino_ratio1,
                                        '特雷诺指数': treynor_ratio, '詹森指数': jensen_alpha, 'M2测度': m2_measure,
                                        '信息比率': information_ratio, "Stutzer指数": stutzer_index

                                        }
        print(basic_factor_dict_chinse)
        basic_factor_dict_chinse["基金名称"] = df_one.product_name[1]
        print(basic_factor_dict_chinse)
        dataList.append(basic_factor_dict_chinse)
        # except Exception as e:
        #     print('出错名称')
        #     print(df_one.product_name[1])
        #     print(e)
    return dataList


a = basic_factor(start_date, end_date, ['000021'], index_benchmark_code, alpha=True)
a = pd.DataFrame(a)
def rank_and_percent(columns_data):
    # 将列表或序列转换为DataFrame
    df = columns_data
    columns_name=columns_data.columns[0]

    # 计算排名,排名越大表示夏普比率越高
    df[columns_name+'_Rank'] = df[columns_name].rank(method='max', ascending=False)

    # 计算排名百分比
    total_count = len(df)
    df[columns_name+'_Percentile'] = (1-(df[columns_name+'_Rank'] / (total_count + 1))) * 100

    return df[[columns_name, columns_name+'_Rank', columns_name+'_Percentile']]


# # 示例数据
# sharpe_ratios = [0.5, 1.2, 0.9, 1.2, 0.6]
# df=pd.DataFrame({'xp': [0.5, 1.2, 0.9, 1.2, 0.6]})
# # 调用函数并打印结果
# ranked_sharpe_ratios = rank_and_percent(df)
# df1=pd.DataFrame({'xp1': [0.5, 1.2, 0.9, 1.2, 0.6]})
# 调用函数并打印结果
b=a["基金名称"]
for name in a.columns[:-1]:
    # 示例数据
    # 调用函数并打印结果
    df1 = pd.DataFrame({name: list(a[name])})
    # 调用函数并打印结果
    ranked_sharpe_ratios1 = rank_and_percent(df1)
    b = pd.merge(b, ranked_sharpe_ratios1, left_index=True, right_index=True)
    # ranked_sharpe_ratios1 = rank_and_percent(a[name],name)
    # b=pd.merge(b,ranked_sharpe_ratios1,left_index=True, right_index=True)
# a.to_csv("a1.csv")
b.to_excel("基金指标3.xlsx")
print(a)

计算风险收益指标

我们将计算包括但不限于以下指标:

  • 绝对收益率
  • 年化收益率
  • 最大回撤
  • 年化波动率
  • 夏普比率
  • 卡玛比率
  • 索提诺比率
  • 特雷诺指数
  • 詹森指数
  • M2测度
  • 信息比率

排名与百分比计算

完成指标计算后,我们将为每项指标添加排名和百分比排名。

def rank_and_percent(df: pd.DataFrame, column_name: str) -> pd.DataFrame:
    df[column_name + '_Rank'] = df[column_name].rank(method='max', ascending=False)
    df[column_name + '_Percentile'] = (1 - (df[column_name + '_Rank'] / (len(df) + 1))) * 100
    return df[[column_name, column_name + '_Rank', column_name + '_Percentile']]
## 综合分析与结果输出

将所有步骤整合到一起,执行基金绩效评估,并将结果输出到Excel文件中。

```python
if __name__ == "__main__":
    start_date = '2023-07-29'
    end_date = '2024-07-29'
    fund_type = '偏股型基金'
    fund_codes = load_fund_info(fund_type)
    benchmark_code = '000300'
    
    fund_performance = basic_factor(start_date, end_date, fund_codes, benchmark_code)
    
    # 对每个指标进行排名和百分比计算
    for column in fund_performance.columns:
        fund_performance = rank_and_percent(fund_performance, column)
    
    fund_performance.to_excel("Fund_Performance_Evaluation.xlsx")
    print(fund_performance)

总结

本文详细介绍了如何使用Python进行单基金绩效评估的全过程。从数据获取、数据库连接、基金信息加载,到绩效指标计算、排名与百分比计算,以及最终的结果输出,每一步都为投资者和基金经理提供了宝贵的参考信息。希望本文能帮助读者更好地理解和应用基金绩效评估的相关技术。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值