Commit 73060184 authored by 赵杰's avatar 赵杰

新版本推荐基金,支持ifa导入基金

parent 0fecd195
...@@ -29,6 +29,8 @@ class UserCustomerDataAdaptor: ...@@ -29,6 +29,8 @@ class UserCustomerDataAdaptor:
end_date = "" end_date = ""
group_data = {} group_data = {}
trade_cal_date = None trade_cal_date = None
all_fund_id_list = None
all_fund_type_dict = None
all_fund_distribution = {} all_fund_distribution = {}
all_fund_performance = {} all_fund_performance = {}
...@@ -38,7 +40,7 @@ class UserCustomerDataAdaptor: ...@@ -38,7 +40,7 @@ class UserCustomerDataAdaptor:
self.compare_index_id = index_id self.compare_index_id = index_id
self.valueSex = '' self.valueSex = ''
p_end_date = pd.to_datetime(end_date).date() p_end_date = pd.to_datetime(end_date).date()
p_end_date = datetime.date(year=p_end_date.year, month=p_end_date.month, day=1) - datetime.timedelta(days=1) # p_end_date = datetime.date(year=p_end_date.year, month=p_end_date.month, day=1) - datetime.timedelta(days=1)
self.end_date = pd.to_datetime(str(p_end_date)) self.end_date = pd.to_datetime(str(p_end_date))
# self.end_date = pd.to_datetime("2020-12-25") # self.end_date = pd.to_datetime("2020-12-25")
p_start_date = datetime.date(year=p_end_date.year, month=p_end_date.month, day=1) p_start_date = datetime.date(year=p_end_date.year, month=p_end_date.month, day=1)
...@@ -104,6 +106,20 @@ class UserCustomerDataAdaptor: ...@@ -104,6 +106,20 @@ class UserCustomerDataAdaptor:
if len(fund_df) > 0: if len(fund_df) > 0:
product_df = product_df.append(fund_df) product_df = product_df.append(fund_df)
product_df = product_df.drop_duplicates("fund_id") product_df = product_df.drop_duplicates("fund_id")
fund_type3 = order_df[order_df["type"] == 3]
if len(fund_type3) > 0:
type3_fund_id = list(fund_type3["fund_id"].unique())
fund_list_str_3 = str(type3_fund_id).replace("[", "(").replace("]", ")")
sql_fund_3 = "select distinct `id`, `fund_name`, `substrategy` from `ifa_imported_fund_info` where `id` in {}".format(
fund_list_str_3)
cur = tamp_fund_session.execute(sql_fund_3)
data = cur.fetchall()
fund_df_3 = pd.DataFrame(list(data), columns=['fund_id', 'fund_name', 'substrategy'])
fund_df_3["freq"] = 1
if len(fund_df_3) > 0:
product_df = product_df.append(fund_df_3)
product_df = product_df.drop_duplicates("fund_id")
user_customer_order_df = order_df.set_index('fund_id').join(product_df.set_index('fund_id')).reset_index() user_customer_order_df = order_df.set_index('fund_id').join(product_df.set_index('fund_id')).reset_index()
user_customer_order_df["confirm_share_date"] = user_customer_order_df["confirm_share_date"].apply(lambda x: pd.to_datetime(x.date())) user_customer_order_df["confirm_share_date"] = user_customer_order_df["confirm_share_date"].apply(lambda x: pd.to_datetime(x.date()))
...@@ -168,6 +184,10 @@ class UserCustomerDataAdaptor: ...@@ -168,6 +184,10 @@ class UserCustomerDataAdaptor:
sql = """select distinct `price_date`, `nav`,`cumulative_nav` from `fund_nav` where `fund_id`='{}' order by `price_date` ASC""".format( sql = """select distinct `price_date`, `nav`,`cumulative_nav` from `fund_nav` where `fund_id`='{}' order by `price_date` ASC""".format(
cur_fund_id) cur_fund_id)
cur = tamp_product_session.execute(sql) cur = tamp_product_session.execute(sql)
elif fund_type == 3:
sql = """select distinct `price_date`, `nav`,`cumulative_nav` from `ifa_imported_fund_nav` where `fund_id`='{}' order by `price_date` ASC""".format(
cur_fund_id)
cur = tamp_fund_session.execute(sql)
data = cur.fetchall() data = cur.fetchall()
cur_fund_nav_df = pd.DataFrame(list(data), columns=['price_date', 'nav', 'cnav']) cur_fund_nav_df = pd.DataFrame(list(data), columns=['price_date', 'nav', 'cnav'])
...@@ -342,6 +362,8 @@ class UserCustomerDataAdaptor: ...@@ -342,6 +362,8 @@ class UserCustomerDataAdaptor:
cnav_df = p_cnav_df[p_cnav_df.index >= start_date].copy() cnav_df = p_cnav_df[p_cnav_df.index >= start_date].copy()
p_fund_id_list = list(p_order_df["fund_id"].unique()) p_fund_id_list = list(p_order_df["fund_id"].unique())
self.all_fund_id_list = p_fund_id_list
self.all_fund_type_dict = {values["fund_id"]: values["type"] for values in p_order_df[["fund_id", "type"]].drop_duplicates().to_dict(orient='records')}
for p_fund_id in p_fund_id_list: for p_fund_id in p_fund_id_list:
order_min_date = p_order_df[p_order_df["fund_id"] == p_fund_id]["confirm_share_date"].min() order_min_date = p_order_df[p_order_df["fund_id"] == p_fund_id]["confirm_share_date"].min()
if pd.to_datetime(order_min_date) > start_date: if pd.to_datetime(order_min_date) > start_date:
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
import warnings import warnings
warnings.filterwarnings("ignore") warnings.filterwarnings("ignore")
from itertools import combinations
from app.utils.fund_rank import * from app.utils.fund_rank import *
from app.utils.risk_parity import * from app.utils.risk_parity import *
from app.pypfopt import risk_models from app.pypfopt import risk_models
...@@ -246,32 +246,45 @@ def get_tamp_fund(): ...@@ -246,32 +246,45 @@ def get_tamp_fund():
return df return df
def get_tamp_nav(fund, start_date, rollback=False, invest_type='public'): def get_tamp_nav(fund, start_date, rollback=False, invest_type=2):
"""获取基金ID为fund, 起始日期为start_date, 终止日期为当前日期的基金净值表 """获取基金ID为fund, 起始日期为start_date, 终止日期为当前日期的基金净值表
Args: Args:
fund[str]:基金ID fund[str]:基金ID
start_date[date]:起始日期 start_date[date]:起始日期
rollback[bool]:当起始日期不在净值公布日历中,是否往前取最近的净值公布日 rollback[bool]:当起始日期不在净值公布日历中,是否往前取最近的净值公布日
public[bool]:是否为公募 invest_type[num]:0:公募 1:私募 2:优选
Returns:df[DataFrame]: 索引为净值公布日, 列为复权净值的净值表; 查询失败则返回None Returns:df[DataFrame]: 索引为净值公布日, 列为复权净值的净值表; 查询失败则返回None
""" """
with TAMP_SQL(tamp_product_engine) as tamp_product: with TAMP_SQL(tamp_product_engine) as tamp_product, TAMP_SQL(tamp_fund_engine) as tamp_fund:
tamp_product_session = tamp_product.session tamp_product_session = tamp_product.session
if invest_type == "private": tamp_fund_session = tamp_fund.session
sql = "SELECT fund_id, price_date, cumulative_nav FROM fund_nav " \ # if invest_type == "private":
"WHERE fund_id='{}'".format(fund) # sql = "SELECT fund_id, price_date, cumulative_nav FROM fund_nav " \
# df = pd.read_sql(sql, con).dropna(how='any') # "WHERE fund_id='{}'".format(fund)
# # df = pd.read_sql(sql, con).dropna(how='any')
# cur = tamp_product_session.execute(sql)
if invest_type == 0:
sql = """select distinct `id`, `end_date`, `accum_nav` from `public_fund_nav` where `id`='{}' order by `end_date` ASC""".format(
fund)
cur = tamp_fund_session.execute(sql)
elif invest_type == 1:
sql = """select distinct `fund_id`, `price_date`,`cumulative_nav` from `fund_nav` where `fund_id`='{}' order by `price_date` ASC""".format(
fund)
cur = tamp_fund_session.execute(sql)
elif invest_type == 2:
sql = """select distinct `fund_id`,`price_date`,`cumulative_nav` from `fund_nav` where `fund_id`='{}' order by `price_date` ASC""".format(
fund)
cur = tamp_product_session.execute(sql) cur = tamp_product_session.execute(sql)
data = cur.fetchall() elif invest_type == 3:
df = pd.DataFrame(data, columns=['fund_id', 'price_date', 'cumulative_nav']).dropna(how='any') sql = """select distinct `fund_id`,`price_date`,`cumulative_nav` from `ifa_imported_fund_nav` where `fund_id`='{}' order by `price_date` ASC""".format(
df.rename({'price_date': 'end_date', 'cumulative_nav': 'adj_nav'}, axis=1, inplace=True) fund)
cur = tamp_fund_session.execute(sql)
# if df2['adj_nav'].count() == 0: data = cur.fetchall()
# logging.log(logging.ERROR, "CAN NOT FIND {}".format(fund)) df = pd.DataFrame(data, columns=['fund_id', 'price_date', 'cumulative_nav']).dropna(how='any')
# return None df.rename({'price_date': 'end_date', 'cumulative_nav': 'adj_nav'}, axis=1, inplace=True)
df['end_date'] = pd.to_datetime(df['end_date']) df['end_date'] = pd.to_datetime(df['end_date'])
...@@ -324,35 +337,44 @@ def get_radar_data(fund): ...@@ -324,35 +337,44 @@ def get_radar_data(fund):
{'name': '综合评分', 'data': '%.2f' % total_score}]} {'name': '综合评分', 'data': '%.2f' % total_score}]}
def get_fund_name(fund): def get_fund_name(fund, fund_type=1):
with TAMP_SQL(tamp_fund_engine) as tamp_fund: if fund_type == 0:
tamp_fund_session = tamp_fund.session with TAMP_SQL(tamp_fund_engine) as tamp_fund:
sql = "SELECT fund_short_name FROM fund_info WHERE id='{}'".format(fund) tamp_fund_session = tamp_fund.session
# df = pd.read_sql(sql, con) sql = "SELECT name FROM public_fund_basic WHERE ts_code='{}'".format(fund)
cur = tamp_fund_session.execute(sql) # df = pd.read_sql(sql, con)
data = cur.fetchall() cur = tamp_fund_session.execute(sql)
df = pd.DataFrame(list(data), columns=['fund_short_name']) data = cur.fetchall()
if len(df) == 0: df = pd.DataFrame(list(data), columns=['fund_short_name'])
with TAMP_SQL(tamp_product_engine) as tamp_product: return df
tamp_product_session = tamp_product.session elif fund_type == 1 or fund_type == 2:
sql = "SELECT fund_short_name FROM fund_info WHERE id='{}'".format(fund) with TAMP_SQL(tamp_fund_engine) as tamp_fund:
# df = pd.read_sql(sql, con) tamp_fund_session = tamp_fund.session
cur = tamp_product_session.execute(sql) sql = "SELECT fund_short_name FROM fund_info WHERE id='{}'".format(fund)
data = cur.fetchall() # df = pd.read_sql(sql, con)
df = pd.DataFrame(list(data), columns=['fund_short_name']) cur = tamp_fund_session.execute(sql)
return df data = cur.fetchall()
return df df = pd.DataFrame(list(data), columns=['fund_short_name'])
if len(df) == 0:
with TAMP_SQL(tamp_product_engine) as tamp_product:
tamp_product_session = tamp_product.session
sql = "SELECT fund_short_name FROM fund_info WHERE id='{}'".format(fund)
# df = pd.read_sql(sql, con)
cur = tamp_product_session.execute(sql)
data = cur.fetchall()
df = pd.DataFrame(list(data), columns=['fund_short_name'])
return df
return df
else:
with TAMP_SQL(tamp_fund_engine) as tamp_fund:
tamp_fund_session = tamp_fund.session
sql = "SELECT fund_name FROM ifa_imported_fund_info WHERE id='{}'".format(fund)
# df = pd.read_sql(sql, con)
cur = tamp_fund_session.execute(sql)
data = cur.fetchall()
df = pd.DataFrame(list(data), columns=['fund_short_name'])
return df
def get_index_name(index_id):
with TAMP_SQL(tamp_fund_engine) as tamp_fund:
tamp_fund_session = tamp_fund.session
sql = "SELECT `name` FROM index_basic WHERE `ts_code`='{}'".format(index_id)
# df = pd.read_sql(sql, con)
cur = tamp_fund_session.execute(sql)
data = cur.fetchall()
df = pd.DataFrame(list(data), columns=['index_name'])
return df
# 获取排名信息 # 获取排名信息
fund_rank = get_fund_rank() fund_rank = get_fund_rank()
...@@ -379,7 +401,8 @@ class PortfolioDiagnose(object): ...@@ -379,7 +401,8 @@ class PortfolioDiagnose(object):
self.freq_list = [] self.freq_list = []
self.client_type = client_type self.client_type = client_type
self.portfolio = portfolio self.portfolio = list(portfolio.keys())
self.portfolio_dict = portfolio
self.expect_return = expect_return self.expect_return = expect_return
self.expect_drawdown = expect_drawdown self.expect_drawdown = expect_drawdown
self.index_id = index_id self.index_id = index_id
...@@ -415,11 +438,11 @@ class PortfolioDiagnose(object): ...@@ -415,11 +438,11 @@ class PortfolioDiagnose(object):
""" """
# 获取原始投资组合的第一支基金的净值表 # 获取原始投资组合的第一支基金的净值表
prod = get_tamp_nav(self.portfolio[0], self.start_date, invest_type=self.invest_type) prod = get_tamp_nav(self.portfolio[0], self.start_date, invest_type=self.portfolio_dict[self.portfolio[0]])
fund_info = get_fund_info(self.end_date, invest_type=self.invest_type) fund_info = get_fund_info(self.end_date, invest_type=self.invest_type)
while prod is None or prod.index[-1] - prod.index[0] < 0.6 * (self.end_date - self.start_date): # while prod is None or prod.index[-1] - prod.index[0] < 0.6 * (self.end_date - self.start_date):
# while prod is None: while prod is None:
# 获取的净值表为空时首先考虑基金净值数据不足半年,查找同一基金经理下的相同二级策略的基金ID作替换 # 获取的净值表为空时首先考虑基金净值数据不足半年,查找同一基金经理下的相同二级策略的基金ID作替换
result = fund_info[fund_info['fund_id'] == self.portfolio[0]] result = fund_info[fund_info['fund_id'] == self.portfolio[0]]
if result.empty: if result.empty:
...@@ -438,7 +461,7 @@ class PortfolioDiagnose(object): ...@@ -438,7 +461,7 @@ class PortfolioDiagnose(object):
# 替换基金数据为空则记录当前基金为找不到数据的基金, 继续尝试获取下一个基金ID的净值表 # 替换基金数据为空则记录当前基金为找不到数据的基金, 继续尝试获取下一个基金ID的净值表
self.no_data_fund.append(self.portfolio[0]) self.no_data_fund.append(self.portfolio[0])
self.portfolio.pop(0) self.portfolio.pop(0)
prod = get_tamp_nav(self.portfolio[0], self.start_date, invest_type=self.invest_type) prod = get_tamp_nav(self.portfolio[0], self.start_date, invest_type=self.portfolio_dict[self.portfolio[0]])
# 记录基金的公布频率 # 记录基金的公布频率
self.freq_list.append(get_frequency(prod)) self.freq_list.append(get_frequency(prod))
...@@ -446,7 +469,7 @@ class PortfolioDiagnose(object): ...@@ -446,7 +469,7 @@ class PortfolioDiagnose(object):
# 循环拼接基金净值表构建组合 # 循环拼接基金净值表构建组合
for idx in range(len(self.portfolio) - 1): for idx in range(len(self.portfolio) - 1):
prod1 = get_tamp_nav(self.portfolio[idx + 1], self.start_date, invest_type=self.invest_type) prod1 = get_tamp_nav(self.portfolio[idx + 1], self.start_date, invest_type=self.portfolio_dict[self.portfolio[idx+1]])
# if prod1 is None or prod1.index[-1] - prod1.index[0] < 0.6 * (self.end_date - self.start_date): # if prod1 is None or prod1.index[-1] - prod1.index[0] < 0.6 * (self.end_date - self.start_date):
if prod1 is None: if prod1 is None:
...@@ -477,12 +500,15 @@ class PortfolioDiagnose(object): ...@@ -477,12 +500,15 @@ class PortfolioDiagnose(object):
# 对所有合并后的基金净值表按最大周期进行重采样 # 对所有合并后的基金净值表按最大周期进行重采样
prod.sort_index(inplace=True) prod.sort_index(inplace=True)
prod = prod.astype(float).interpolate()
prod.bfill(inplace=True)
prod.ffill(inplace=True) prod.ffill(inplace=True)
prod = resample(prod, get_trade_cal(), min(self.freq_list)) # prod = resample(prod, get_trade_cal(), min(self.freq_list))
if 'cal_date' in prod.columns: if 'cal_date' in prod.columns:
prod.drop(labels='cal_date', inplace=True, axis=1) prod.drop(labels='cal_date', inplace=True, axis=1)
if 'end_date' in prod.columns: if 'end_date' in prod.columns:
prod.drop(labels='end_date', inplace=True, axis=1) prod.drop(labels='end_date', inplace=True, axis=1)
prod.fillna(method='bfill', inplace=True)
prod.dropna(how='any', inplace=True) prod.dropna(how='any', inplace=True)
return prod return prod
...@@ -495,7 +521,7 @@ class PortfolioDiagnose(object): ...@@ -495,7 +521,7 @@ class PortfolioDiagnose(object):
Returns: 剔除建议替换基金的组合净值表 Returns: 剔除建议替换基金的组合净值表
""" """
self.old_correlation = cal_correlation(prod) self.old_correlation = cal_correlation(prod.fillna(method='bfill'))
for fund in prod.columns: for fund in prod.columns:
z_score = search_rank(fund_rank, fund, metric='z_score') z_score = search_rank(fund_rank, fund, metric='z_score')
...@@ -508,9 +534,43 @@ class PortfolioDiagnose(object): ...@@ -508,9 +534,43 @@ class PortfolioDiagnose(object):
self.abandon_fund_corr.append(fund) self.abandon_fund_corr.append(fund)
prod = prod.drop(self.abandon_fund_score + self.abandon_fund_corr, axis=1) prod = prod.drop(self.abandon_fund_score + self.abandon_fund_corr, axis=1)
if prod.empty:
prod = pd.DataFrame()
self.freq_list = []
self.old_correlation = self.old_correlation.fillna(1).round(2) self.old_correlation = self.old_correlation.fillna(1).round(2)
self.old_correlation.columns = self.old_correlation.columns.map(lambda x: get_fund_name(x).values[0][0]) self.old_correlation.columns = self.old_correlation.columns.map(lambda x: get_fund_name(x, self.portfolio_dict[x]).values[0][0])
self.old_correlation.index = self.old_correlation.index.map(lambda x: get_fund_name(x).values[0][0]) self.old_correlation.index = self.old_correlation.index.map(lambda x: get_fund_name(x, self.portfolio_dict[x]).values[0][0])
return prod
def product_filter(self, candidate_funds, prod):
for proposal in candidate_funds:
s_date = cal_date(self.end_date, 'Y', 1)
proposal_nav = get_tamp_nav(proposal, s_date, invest_type=2)
# 忽略净值周期大于周更的产品
# if get_frequency(proposal_nav) <= 52:
# continue
self.freq_list.append(get_frequency(proposal_nav))
proposal_nav = rename_col(proposal_nav, proposal)
# 按最大周期进行重采样,计算新建组合的相关性
if prod.empty:
temp = proposal_nav
else:
temp = pd.merge(prod, proposal_nav, how='outer', on='end_date').astype(float)
temp.sort_index(inplace=True)
temp.ffill(inplace=True)
temp = resample(temp, get_trade_cal(), min(self.freq_list))
temp_correlation = cal_correlation(temp)
judge_correlation = temp_correlation.fillna(0)
if np.all(judge_correlation < 0.8):
# self.proposal_fund.append(proposal)
prod = temp
else:
self.freq_list.pop(-1)
return prod return prod
def proposal(self, prod): def proposal(self, prod):
...@@ -522,24 +582,6 @@ class PortfolioDiagnose(object): ...@@ -522,24 +582,6 @@ class PortfolioDiagnose(object):
Returns: 增加建议申购基金的组合净值表 Returns: 增加建议申购基金的组合净值表
""" """
# 组合内已包含的策略
# included_strategy = set()
# 按每种基金最少投资100w确定组合包含的最大基金数量
max_len = min(len(self.portfolio) - len(prod.columns), self.invest_amount/1e6)
# 排名表内包含的所有策略
# all_strategy = set(fund_rank['substrategy'].to_list())
all_risk = {"H", "M", "L"}
included_risk = {}
if prod is not None:
# included_strategy = set([search_rank(fund_rank, fund, metric='substrategy') for fund in prod.columns])
included_risk = set([get_risk_level(search_rank(fund_rank, fund, metric='substrategy'))
for fund in prod.columns])
# 待添加策略为所有策略-组合已包含策略
# add_strategy = all_strategy - included_strategy
add_risk = all_risk - included_risk
candidate_funds = tamp_fund['fund_id'].to_list() candidate_funds = tamp_fund['fund_id'].to_list()
candidate_info = [] candidate_info = []
for proposal in candidate_funds: for proposal in candidate_funds:
...@@ -556,77 +598,13 @@ class PortfolioDiagnose(object): ...@@ -556,77 +598,13 @@ class PortfolioDiagnose(object):
# candidate_low_risk = [i[0] for i in list(filter(lambda x: x[2] == 'L', candidate_info))] # candidate_low_risk = [i[0] for i in list(filter(lambda x: x[2] == 'L', candidate_info))]
candidate_funds = [i[0] for i in candidate_info] candidate_funds = [i[0] for i in candidate_info]
# 遍历产品池,推荐得分>80且与组合内其他基金相关度低于0.8的属于待添加策略的基金 prod = self.product_filter(candidate_funds, prod)
# for proposal in candidate_funds:
# proposal_strategy = fund_rank[fund_rank['fund_id'] == proposal]['substrategy'].values[0]
# if get_risk_level(proposal_strategy) in add_risk or not add_risk:
# # if proposal_z_score > 80:
# proposal_nav = get_tamp_nav(proposal, self.start_date, invest_type=self.invest_type)
# # 忽略净值周期大于周更的产品
# # if get_frequency(proposal_nav) <= 52:
# # continue
#
# self.freq_list.append(get_frequency(proposal_nav))
# proposal_nav = rename_col(proposal_nav, proposal)
#
# # 按最大周期进行重采样,计算新建组合的相关性
# prod = pd.merge(prod, proposal_nav, how='outer', on='end_date').astype(float)
# prod.sort_index(inplace=True)
# prod.ffill(inplace=True)
# prod = resample(prod, get_trade_cal(), min(self.freq_list))
#
# self.new_correlation = cal_correlation(prod)
# judge_correlation = self.new_correlation.fillna(0)
#
# if np.all(judge_correlation < 0.8):
# self.proposal_fund.append(proposal)
# max_len -= 1
# # add_strategy -= {proposal_strategy}
# add_risk -= {get_risk_level(proposal_strategy)}
# # if len(add_strategy) == 0 or max_len == 0:
# if max_len == 0:
# break
# else:
# prod.drop(columns=proposal, inplace=True)
# 遍历产品池,推荐得分>80且与组合内其他基金相关度低于0.8的属于待添加策略的基金
for proposal in candidate_funds:
proposal_nav = get_tamp_nav(proposal, self.start_date, invest_type=self.invest_type)
# 忽略净值周期大于周更的产品
# if get_frequency(proposal_nav) <= 52:
# continue
self.freq_list.append(get_frequency(proposal_nav))
proposal_nav = rename_col(proposal_nav, proposal)
# 按最大周期进行重采样,计算新建组合的相关性
temp = pd.merge(prod, proposal_nav, how='outer', on='end_date').astype(float)
temp.sort_index(inplace=True)
temp.ffill(inplace=True)
temp = resample(temp, get_trade_cal(), min(self.freq_list))
if "cal_date" in temp.columns:
temp.drop(labels=['cal_date', 'end_date'], axis=1, inplace=True)
self.new_correlation = cal_correlation(temp)
judge_correlation = self.new_correlation.fillna(0)
if np.all(judge_correlation < 0.8):
self.proposal_fund.append(proposal)
max_len -= 1
# add_strategy -= {proposal_strategy}
add_risk -= {get_risk_level(proposal_strategy)}
# if len(add_strategy) == 0 or max_len == 0:
prod = temp
if max_len == 0:
break
prod.dropna(how='all', inplace=True) prod.dropna(how='all', inplace=True)
prod.fillna(method='bfill', inplace=True) prod.fillna(method='bfill', inplace=True)
self.new_correlation = self.new_correlation.fillna(1).round(2)
self.new_correlation.columns = self.new_correlation.columns.map(lambda x: get_fund_name(x).values[0][0])
self.new_correlation.index = self.new_correlation.index.map(lambda x: get_fund_name(x).values[0][0])
return prod return prod
def optimize(self, ): def optimize(self, ):
import time import time
start = time.time() start = time.time()
...@@ -636,72 +614,101 @@ class PortfolioDiagnose(object): ...@@ -636,72 +614,101 @@ class PortfolioDiagnose(object):
self.abandoned_portfolio = self.abandon(self.origin_portfolio) self.abandoned_portfolio = self.abandon(self.origin_portfolio)
end2 = time.time() end2 = time.time()
print("计算换仓基金时间:", end2 - end1) print("计算换仓基金时间:", end2 - end1)
self.propose_portfolio = self.proposal(self.abandoned_portfolio) # self.propose_portfolio = self.proposal(self.abandoned_portfolio)
prod = self.proposal(self.abandoned_portfolio)
end3 = time.time() end3 = time.time()
print("遍历产品池获取候选推荐时间:", end3 - end2) print("遍历产品池获取候选推荐时间:", end3 - end2)
# propose_portfolio.to_csv('test_portfolio.csv', encoding='gbk') # propose_portfolio.to_csv('test_portfolio.csv', encoding='gbk')
mu = [search_rank(fund_rank, x, 'annual_return') for x in self.propose_portfolio.columns] prod_risk_zip = []
S = risk_models.sample_cov(self.propose_portfolio, frequency=min(self.freq_list)) for fund in prod.columns:
dd = [search_rank(fund_rank, x, 'max_drawdown') for x in self.propose_portfolio.columns] prod_risk_zip.append((fund, str(get_risk_level(search_rank(fund_rank, fund, metric='substrategy')))))
# if self.client_type == 1:
# proposal_risk = [[x, get_risk_level(search_rank(fund_rank, x, metric='substrategy'))] for x in
# propose_portfolio.columns]
# self.proposal_fund = list(filter(lambda x: x[1] != 'H', proposal_risk))
# drop_fund_list = list(filter(lambda x: x[1] = 'H', proposal_risk))
# proposal_portfolio = list((set(self.portfolio) - set(self.no_data_fund) - set(self.replace_pair.keys())) | \
# (set(self.proposal_fund) | set(self.replace_pair.values())))
# propose_portfolio.drop()
propose_risk_mapper = dict() propose_risk_mapper = dict()
for fund in self.propose_portfolio.columns: for fund in prod.columns:
propose_risk_mapper[fund] = str(get_risk_level(search_rank(fund_rank, fund, metric='substrategy'))) propose_risk_mapper[fund] = str(get_risk_level(search_rank(fund_rank, fund, metric='substrategy')))
if self.client_type == 1: if self.client_type == 1:
risk_upper = {"M": 0.4, "H": 0.0} risk_upper = {"M": 0.4, "H": 0.0}
risk_lower = {"L": 0.6} risk_lower = {"L": 0.6}
self.expect_return = 0.12 self.expect_return = 0.08
self.expect_drawdown = 0.03 self.expect_drawdown = 0.03
prod_high_risk = [i[0] for i in list(filter(lambda x: x[1] == 'H', prod_risk_zip))]
prod.drop(columns=prod_high_risk, axis=1, inplace=True)
elif self.client_type == 2: elif self.client_type == 2:
risk_upper = {"H": 0.2} risk_upper = {"H": 0.2}
risk_lower = {"L": 0.5, "M": 0.3} risk_lower = {"L": 0.5, "M": 0.3}
self.expect_return = 0.15 self.expect_return = 0.10
self.expect_drawdown = 0.05 self.expect_drawdown = 0.05
elif self.client_type == 3: elif self.client_type == 3:
risk_upper = {"L": 0.3, "H": 0.3} risk_upper = {"L": 0.3, "H": 0.3}
risk_lower = {"M": 0.4} risk_lower = {"M": 0.4}
self.expect_return = 0.18 self.expect_return = 0.12
self.expect_drawdown = 0.08 self.expect_drawdown = 0.08
elif self.client_type == 4: elif self.client_type == 4:
risk_upper = {"L": 0.2, "M": 0.4} risk_upper = {"L": 0.2, "M": 0.4}
risk_lower = {"H": 0.4} risk_lower = {"H": 0.4}
self.expect_return = 0.15 self.expect_return = 0.15
self.expect_drawdown = 0.20 self.expect_drawdown = 0.10
elif self.client_type == 5: elif self.client_type == 5:
risk_upper = {"L": 0.0, "M": 0.4} risk_upper = {"L": 0.0, "M": 0.4}
risk_lower = {"H": 0.6} risk_lower = {"H": 0.6}
self.expect_return = 0.25 self.expect_return = 0.20
self.expect_drawdown = 0.15 self.expect_drawdown = 0.20
else: prod_low_risk = [i[0] for i in list(filter(lambda x: x[1] == 'L', prod_risk_zip))]
risk_upper = {"H": 1.0} prod.drop(columns=prod_low_risk, axis=1, inplace=True)
risk_lower = {"L": 0.0}
raise ValueError candidate_funds = list((set(prod.columns) - set(self.no_data_fund) - set(self.replace_pair.keys())) |
set(self.replace_pair.values()))
print(candidate_funds)
max_len = int(self.invest_amount / 1e6)
w_low = 1000000.0 / self.invest_amount
weights_sharp_list = []
for i in range(1, max_len):
proposal_fund_combinations = list(combinations(candidate_funds, r=i))
for proposal_funds in proposal_fund_combinations:
drop_funds = list(set(candidate_funds) - set(proposal_funds))
temp = prod.drop(columns=drop_funds, axis=1)
mu = [search_rank(fund_rank, x, 'annual_return') for x in temp.columns]
S = risk_models.sample_cov(temp, frequency=min(self.freq_list))
dd = [search_rank(fund_rank, x, 'max_drawdown') for x in temp.columns]
w_low = 1000000 / self.invest_amount try:
try: ef = EfficientFrontier(mu, S, weight_bounds=[w_low, 1], expected_drawdown=dd)
ef = EfficientFrontier(mu, S, weight_bounds=[w_low, 1], expected_drawdown=dd) ef.add_sector_constraints(propose_risk_mapper, risk_lower, risk_upper)
# ef = EfficientFrontier(mu, S, weight_bounds=[0, 1], expected_drawdown=dd) # ef.efficient_return(target_return=self.expect_return, target_drawdown=self.expect_drawdown)
ef.add_sector_constraints(propose_risk_mapper, risk_lower, risk_upper) ef.efficient_drawdown(drawdown_limit=self.expect_drawdown)
ef.efficient_return(target_return=self.expect_return, target_drawdown=self.expect_drawdown) clean_weights = ef.clean_weights()
clean_weights = ef.clean_weights() mu, sigma, sharp = ef.portfolio_performance(verbose=True)
ef.portfolio_performance(verbose=True) # self.new_weights = np.array(list(clean_weights.values()))
self.new_weights = np.array(list(clean_weights.values())) weights_sharp_list.append([clean_weights, sharp])
except: # 保留的基金是否必须在新组合中
self.new_weights = np.asarray([1/len(self.propose_portfolio.columns)] * len(self.propose_portfolio.columns)) # if len(set(clean_weights.keys) | set(maintain_funds)) == len(set(clean_weights.keys)):
# print(clean_weights)
break
except:
continue
# self.new_weights = np.asarray([1/len(self.propose_portfolio.columns)] * len(self.propose_portfolio.columns))
weights_sharp_list.sort(key=lambda x: x[1], reverse=True)
print(weights_sharp_list)
max_sharp_weights = weights_sharp_list[0][0]
self.proposal_fund = list(max_sharp_weights.keys())
self.propose_portfolio = prod.filter(items=self.proposal_fund)
self.propose_portfolio.fillna(method="bfill", inplace=True)
self.propose_portfolio.fillna(method="ffill", inplace=True)
self.new_weights = np.array(list(max_sharp_weights.values()))
self.new_correlation = cal_correlation(self.propose_portfolio)
# self.new_correlation = self.new_correlation[self.new_correlation > 0.8] = np.random.uniform(0.75, 0.78)
self.new_correlation = self.new_correlation.fillna(1).round(2)
self.new_correlation.columns = self.new_correlation.columns.map(lambda x: get_fund_name(x).values[0][0])
self.new_correlation.index = self.new_correlation.index.map(lambda x: get_fund_name(x).values[0][0])
print(self.new_weights)
end4 = time.time() end4 = time.time()
print("模型计算一次时间:", end4 - end3) print("模型计算一次时间:", end4 - end3)
# S = np.asmatrix(S) # S = np.asmatrix(S)
...@@ -1071,9 +1078,9 @@ class PortfolioDiagnose(object): ...@@ -1071,9 +1078,9 @@ class PortfolioDiagnose(object):
# 在保留{}的基础上,建议赎回{},并增配{}后,整体组合波动率大幅降低,最大回撤从{}降到不足{},年化收益率提升{}个点 # 在保留{}的基础上,建议赎回{},并增配{}后,整体组合波动率大幅降低,最大回撤从{}降到不足{},年化收益率提升{}个点
hold_fund = set(self.portfolio) - set(self.abandon_fund_score + self.abandon_fund_corr + self.no_data_fund) hold_fund = set(self.portfolio) - set(self.abandon_fund_score + self.abandon_fund_corr + self.no_data_fund)
hold_fund_name = [get_fund_name(x).values[0][0] for x in hold_fund] hold_fund_name = [get_fund_name(x, self.portfolio_dict[x]).values[0][0] for x in hold_fund]
abandon_fund = (self.abandon_fund_score + self.abandon_fund_corr) abandon_fund = (self.abandon_fund_score + self.abandon_fund_corr)
abandon_fund_name = [get_fund_name(x).values[0][0] for x in abandon_fund] abandon_fund_name = [get_fund_name(x, self.portfolio_dict[x]).values[0][0] for x in abandon_fund]
proposal_fund = self.proposal_fund proposal_fund = self.proposal_fund
proposal_fund_name = [get_fund_name(x).values[0][0] for x in proposal_fund] proposal_fund_name = [get_fund_name(x).values[0][0] for x in proposal_fund]
...@@ -1098,7 +1105,7 @@ class PortfolioDiagnose(object): ...@@ -1098,7 +1105,7 @@ class PortfolioDiagnose(object):
repr(e) repr(e)
return None, None, None, None, None return None, None, None, None, None
def single_evaluation(self, fund_id, objective=False): def single_evaluation(self, fund_id, fund_id_type=2, objective=False):
""" """
1、该基金整体表现优秀/良好/一般,收益能力优秀/良好/合格/较差,回撤控制能力优秀/良好/合格/较差,风险收益比例较高/一般/较低; 1、该基金整体表现优秀/良好/一般,收益能力优秀/良好/合格/较差,回撤控制能力优秀/良好/合格/较差,风险收益比例较高/一般/较低;
2、在收益方面,该基金年化收益能力高于/持平/低于同类基金平均水平,有x%区间跑赢大盘/指数,绝对收益能力优秀/一般; 2、在收益方面,该基金年化收益能力高于/持平/低于同类基金平均水平,有x%区间跑赢大盘/指数,绝对收益能力优秀/一般;
...@@ -1123,7 +1130,10 @@ class PortfolioDiagnose(object): ...@@ -1123,7 +1130,10 @@ class PortfolioDiagnose(object):
z_score < 70], [0, 1, 2]).item() z_score < 70], [0, 1, 2]).item()
index_return_monthly = get_index_monthly(self.index_id, self.start_date) index_return_monthly = get_index_monthly(self.index_id, self.start_date)
fund_nav = get_tamp_nav(fund_id, self.start_date, invest_type=self.invest_type) t_type = self.portfolio_dict.get(fund_id, None)
if t_type is not None:
fund_id_type = t_type
fund_nav = get_tamp_nav(fund_id, self.start_date, invest_type=fund_id_type)
fund_nav_monthly = fund_nav.groupby([fund_nav.index.year, fund_nav.index.month]).tail(1) fund_nav_monthly = fund_nav.groupby([fund_nav.index.year, fund_nav.index.month]).tail(1)
fund_nav_monthly = rename_col(fund_nav_monthly, fund_id) fund_nav_monthly = rename_col(fund_nav_monthly, fund_id)
fund_return_monthly = simple_return(fund_nav_monthly[fund_id].astype(float)) fund_return_monthly = simple_return(fund_nav_monthly[fund_id].astype(float))
...@@ -1262,7 +1272,7 @@ class PortfolioDiagnose(object): ...@@ -1262,7 +1272,7 @@ class PortfolioDiagnose(object):
def propose_fund_evaluation(self, ): def propose_fund_evaluation(self, ):
try: try:
result = [] result = []
for fund in self.proposal_fund: for fund in self.propose_portfolio.columns:
result.append(self.single_evaluation(fund)) result.append(self.single_evaluation(fund))
return result return result
except Exception as e: except Exception as e:
......
...@@ -19,8 +19,8 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor): ...@@ -19,8 +19,8 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor):
total_result_data = {} total_result_data = {}
group_result_data = {} group_result_data = {}
def __init__(self, user_id, customer_id, end_date=str(datetime.date.today())): def __init__(self, user_id, customer_id):
super().__init__(user_id, customer_id, end_date) super().__init__(user_id, customer_id)
# 组合结果数据 # 组合结果数据
def calculate_group_result_data(self): def calculate_group_result_data(self):
...@@ -57,6 +57,11 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor): ...@@ -57,6 +57,11 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor):
return_ratio_df, month_return_ratio_df, contribution_decomposition = self.combination_yield(resample_cur_folio_result_cnav_data, return_ratio_df, month_return_ratio_df, contribution_decomposition = self.combination_yield(resample_cur_folio_result_cnav_data,
fund_id_list) fund_id_list)
resample_df = resample(return_ratio_df, self.trade_cal_date, freq_max) resample_df = resample(return_ratio_df, self.trade_cal_date, freq_max)
if resample_df.index.values[-1] > self.end_date:
last = resample_df.index.values[-1]
resample_df["index_date"] = resample_df.index
resample_df.loc[last, "index_date"] = self.end_date
resample_df.set_index("index_date", inplace=True)
resample_df = resample_df[resample_df.index <= self.end_date] resample_df = resample_df[resample_df.index <= self.end_date]
...@@ -87,7 +92,7 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor): ...@@ -87,7 +92,7 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor):
# 波动率 # 波动率
volatility_ = volatility(resample_df["cum_return_ratio"], n_freq) volatility_ = volatility(resample_df["cum_return_ratio"], n_freq)
folio_report_data["volatility"] = float(volatility_) folio_report_data["volatility"] = float(volatility_) if not math.isnan(volatility_) else 0.0
# 最大回撤 # 最大回撤
drawdown = max_drawdown(resample_df["cum_return_ratio"]) drawdown = max_drawdown(resample_df["cum_return_ratio"])
...@@ -100,7 +105,7 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor): ...@@ -100,7 +105,7 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor):
sharpe = sharpe_ratio(exc, sim, n_freq) sharpe = sharpe_ratio(exc, sim, n_freq)
except ZeroDivisionError: except ZeroDivisionError:
sharpe = 0.0 sharpe = 0.0
folio_report_data["sharpe"] = float(sharpe) folio_report_data["sharpe"] = float(sharpe) if not math.isnan(sharpe) else 0.0
# 期末资产 # 期末资产
ending_assets = cumulative_profit + total_cost ending_assets = cumulative_profit + total_cost
...@@ -135,7 +140,10 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor): ...@@ -135,7 +140,10 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor):
if len(last_profit_ratio) <= 0: if len(last_profit_ratio) <= 0:
cur_year_profit_ratio = cur_profit_ratio[-1] - 1 cur_year_profit_ratio = cur_profit_ratio[-1] - 1
else: else:
cur_year_profit_ratio = (cur_profit_ratio[-1] - last_profit_ratio[-1]) / last_profit_ratio[-1] if len(cur_profit_ratio) < 1:
cur_year_profit_ratio = 0
else:
cur_year_profit_ratio = (cur_profit_ratio[-1] - last_profit_ratio[-1]) / last_profit_ratio[-1]
folio_report_data["cur_year_profit_ratio"] = float(cur_year_profit_ratio) folio_report_data["cur_year_profit_ratio"] = float(cur_year_profit_ratio)
# 累积收益率 # 累积收益率
...@@ -226,6 +234,11 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor): ...@@ -226,6 +234,11 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor):
resample_cur_folio_result_cnav_data = resample_cur_folio_result_cnav_data[resample_cur_folio_result_cnav_data.index <=self.end_date] resample_cur_folio_result_cnav_data = resample_cur_folio_result_cnav_data[resample_cur_folio_result_cnav_data.index <=self.end_date]
return_ratio_df, month_return_ratio_df, contribution_decomposition = self.combination_yield(resample_cur_folio_result_cnav_data, fund_id_list) return_ratio_df, month_return_ratio_df, contribution_decomposition = self.combination_yield(resample_cur_folio_result_cnav_data, fund_id_list)
resample_df = resample(return_ratio_df, self.trade_cal_date, freq_max) resample_df = resample(return_ratio_df, self.trade_cal_date, freq_max)
if resample_df.index.values[-1] > self.end_date:
last = resample_df.index.values[-1]
resample_df["index_date"] = resample_df.index
resample_df.loc[last, "index_date"] = self.end_date
resample_df.set_index("index_date", inplace=True)
resample_df = resample_df[resample_df.index <= self.end_date] resample_df = resample_df[resample_df.index <= self.end_date]
# 总成本 # 总成本
...@@ -289,7 +302,13 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor): ...@@ -289,7 +302,13 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor):
if len(last_profit_ratio) <= 0: if len(last_profit_ratio) <= 0:
cur_year_profit_ratio = cur_profit_ratio[-1] - 1 cur_year_profit_ratio = cur_profit_ratio[-1] - 1
else: else:
cur_year_profit_ratio = (cur_profit_ratio[-1] - last_profit_ratio[-1]) / last_profit_ratio[-1] if len(cur_profit_ratio) < 1:
cur_year_profit_ratio = 0.0
else:
if len(cur_profit_ratio) < 1:
cur_year_profit_ratio = 0
else:
cur_year_profit_ratio = (cur_profit_ratio[-1] - last_profit_ratio[-1]) / last_profit_ratio[-1]
report_data["cur_year_profit_ratio"] = float(cur_year_profit_ratio) report_data["cur_year_profit_ratio"] = float(cur_year_profit_ratio)
# 月度回报 # 月度回报
...@@ -422,7 +441,7 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor): ...@@ -422,7 +441,7 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor):
# 基金持仓数据 # 基金持仓数据
total_market_values = p_sum_profit + p_total_amount # 月末总市值 total_market_values = p_sum_profit + p_total_amount # 月末总市值
fund_strategy_name = dict_substrategy[int(row["substrategy"])] fund_strategy_name = dict_substrategy[int(row["substrategy"])]
if "长富" in row["fund_name"] or "盈沛" in row["fund_name"] : if "长富" in row["fund_name"] or "盈沛" in row["fund_name"]:
fund_strategy_name = "FOF" fund_strategy_name = "FOF"
fund_hoding_info = {"fund_strategy_name": fund_strategy_name, "fund_name": row["fund_name"]} fund_hoding_info = {"fund_strategy_name": fund_strategy_name, "fund_name": row["fund_name"]}
fund_hoding_info["confirm_date"] = row["confirm_share_date"].strftime("%Y-%m-%d") fund_hoding_info["confirm_date"] = row["confirm_share_date"].strftime("%Y-%m-%d")
...@@ -571,7 +590,7 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor): ...@@ -571,7 +590,7 @@ class UserCustomerResultAdaptor(UserCustomerDataAdaptor):
# 波动率 # 波动率
volatility_ = volatility(fund_nav_df["cum_return_ratio"], n_freq) volatility_ = volatility(fund_nav_df["cum_return_ratio"], n_freq)
result["volatility"] = float(volatility_) result["volatility"] = float(volatility_) if not math.isnan(volatility_) else 0.0
# 最大回撤 # 最大回撤
drawdown = max_drawdown(fund_nav_df["cum_return_ratio"]) drawdown = max_drawdown(fund_nav_df["cum_return_ratio"])
......
...@@ -45,7 +45,6 @@ class DataIntegrate: ...@@ -45,7 +45,6 @@ class DataIntegrate:
# 月度回报表格 # 月度回报表格
self.get_month_table_return() self.get_month_table_return()
# 分组和计算个基点评以及新增基金等结果 # 分组和计算个基点评以及新增基金等结果
def get_group_result(self): def get_group_result(self):
for group_name, group_result in self.d.items(): for group_name, group_result in self.d.items():
...@@ -83,7 +82,10 @@ class DataIntegrate: ...@@ -83,7 +82,10 @@ class DataIntegrate:
self.all_folio_result[group_name] = cur_group_portfolio_result self.all_folio_result[group_name] = cur_group_portfolio_result
def get_portfolio_diagnose(self, portfolio, client_type=1, invest_amount=10000000): def get_portfolio_diagnose(self, portfolio, client_type=1, invest_amount=10000000):
portfolio_diagnose = PortfolioDiagnose(client_type=client_type, portfolio=portfolio, invest_amount=float(invest_amount), if invest_amount < 10000000:
invest_amount = 10000000
portfolio_diagnose = PortfolioDiagnose(client_type=client_type, portfolio=self.user_customer.all_fund_type_dict,
invest_amount=float(invest_amount),
start_date=self.user_customer.start_date) start_date=self.user_customer.start_date)
portfolio_diagnose.optimize() portfolio_diagnose.optimize()
return portfolio_diagnose return portfolio_diagnose
...@@ -299,7 +301,7 @@ class DataIntegrate: ...@@ -299,7 +301,7 @@ class DataIntegrate:
template = env.get_template('/v2/monthReportV2.1.html') # 获取一个模板文件 template = env.get_template('/v2/monthReportV2.1.html') # 获取一个模板文件
monthReport_html = template.render(self.data).replace('None', 'none') # 渲染 monthReport_html = template.render(self.data).replace('None', 'none') # 渲染
# 保存 monthReport_html # 保存 monthReport_html
# save_file = "app/html/monthReport.html" # save_file = "app/pdf/monthReport.html"
# with open(save_file, 'w', encoding="utf-8") as f: # with open(save_file, 'w', encoding="utf-8") as f:
# f.write(monthReport_html) # f.write(monthReport_html)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment