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