diff --git a/app/service/portfolio_diagnose.py b/app/service/portfolio_diagnose.py index 2f96162d5272248727b5e2706e2776cfcf46f4e0..ecbacb27818f33857d512ff712f0afd94e59b24b 100644 --- a/app/service/portfolio_diagnose.py +++ b/app/service/portfolio_diagnose.py @@ -292,6 +292,9 @@ class PortfolioDiagnose(object): self.new_correlation = None self.old_weights = None self.new_weights = None + self.origin_portfolio = None + self.abandoned_portfolio = None + self.propose_portfolio = None def get_portfolio(self, ): """获å–组åˆå‡€å€¼è¡¨ @@ -341,7 +344,7 @@ class PortfolioDiagnose(object): if replaced_fund is not None: prod1 = get_nav(replaced_fund, self.start_date, invest_type=self.invest_type) - self.replace_pair[portfolio[0]] = replaced_fund + self.replace_pair[portfolio[idx + 1]] = replaced_fund self.freq_list.append(get_frequency(prod1)) prod1 = rename_col(prod1, replaced_fund) else: @@ -457,20 +460,20 @@ class PortfolioDiagnose(object): def optimize(self, ): import time start = time.time() - origin_portfolio = self.get_portfolio() + self.origin_portfolio = self.get_portfolio() end1 = time.time() print("åŽŸå§‹ç»„åˆæ•°æ®èŽ·å–æ—¶é—´ï¼š", end1 - start) - abandoned_portfolio = self.abandon(origin_portfolio) + self.abandoned_portfolio = self.abandon(self.origin_portfolio) end2 = time.time() print("计算æ¢ä»“基金时间:", end2 - end1) - propose_portfolio = self.proposal(abandoned_portfolio) + self.propose_portfolio = self.proposal(self.abandoned_portfolio) end3 = time.time() print("éåŽ†äº§å“æ± 获å–å€™é€‰æŽ¨èæ—¶é—´ï¼š", end3 - end2) # propose_portfolio.to_csv('test_portfolio.csv', encoding='gbk') - mu = expected_returns.mean_historical_return(propose_portfolio, frequency=min(self.freq_list)) - S = risk_models.sample_cov(propose_portfolio, frequency=min(self.freq_list)) - dd = expected_returns.drawdown_from_prices(propose_portfolio) + mu = expected_returns.mean_historical_return(self.propose_portfolio, frequency=min(self.freq_list)) + S = risk_models.sample_cov(self.propose_portfolio, frequency=min(self.freq_list)) + dd = expected_returns.drawdown_from_prices(self.propose_portfolio) # if self.client_type == 1: # proposal_risk = [[x, get_risk_level(search_rank(fund_rank, x, metric='substrategy'))] for x in @@ -483,7 +486,7 @@ class PortfolioDiagnose(object): # propose_portfolio.drop() propose_risk_mapper = dict() - for fund in propose_portfolio.columns: + for fund in self.propose_portfolio.columns: propose_risk_mapper[fund] = str(get_risk_level(search_rank(fund_rank, fund, metric='substrategy'))) # risk_upper = {"H": 0.0} @@ -522,13 +525,10 @@ class PortfolioDiagnose(object): def return_compare(self): index_data = get_index_daily(self.index_id) - origin_portfolio = self.get_portfolio() - abandoned_portfolio = self.abandon(origin_portfolio) - propose_portfolio = self.proposal(abandoned_portfolio) - index_data = pd.merge(index_data, propose_portfolio, how='inner', left_index=True, right_index=True) + index_data = pd.merge(index_data, self.propose_portfolio, how='inner', left_index=True, right_index=True) index_return = index_data.iloc[:, :] / index_data.iloc[0, :] - 1 # origin_fund_return = origin_portfolio.iloc[:, :] / origin_portfolio.iloc[0, :] - 1 - propose_fund_return = propose_portfolio.iloc[:, :] / propose_portfolio.iloc[0, :] - 1 + propose_fund_return = self.propose_portfolio.iloc[:, :] / self.propose_portfolio.iloc[0, :] - 1 propose_fund_return['return'] = propose_fund_return.T.iloc[:, :].apply(lambda x: np.dot(self.new_weights, x)) return index_return, propose_fund_return @@ -541,17 +541,17 @@ class PortfolioDiagnose(object): past_month = (current_year - start_year) * 12 + current_month - start_month # æŠ•å…¥æˆæœ¬(万元) - input_cost = round(group_result[group_name]["total_cost"]/10000, 2) + input_cost = round(group_result[group_name]["total_cost"] / 10000, 2) # 整体盈利(万元) - total_profit = round(group_result[group_name]["cumulative_profit"]/10000, 2) + total_profit = round(group_result[group_name]["cumulative_profit"] / 10000, 2) # 整体表现 回撤能力 fund_rank_data = fund_rank[fund_rank["fund_id"].isin(self.portfolio)] z_score = fund_rank_data["z_score"].mean() drawdown_rank = fund_rank_data["max_drawdown_rank"].mean() return_rank_df = fund_rank_data["annual_return_rank"] z_score_level = np.select([z_score >= 80, - 70 <= z_score < 80, - z_score < 70], [0, 1, 2]).item() + 70 <= z_score < 80, + z_score < 70], [0, 1, 2]).item() drawdown_level = np.select([drawdown_rank >= 0.8, 0.7 <= drawdown_rank < 0.8, 0.6 <= drawdown_rank < 0.7, @@ -563,9 +563,10 @@ class PortfolioDiagnose(object): num = len(fund_rank_re) fund_id_rank_list = list(fund_rank_re["fund_id"]) for f_id in fund_id_rank_list: - name = data_adaptor.user_customer_order_df[data_adaptor.user_customer_order_df["fund_id"] == f_id]["fund_name"].values[0] + name = data_adaptor.user_customer_order_df[data_adaptor.user_customer_order_df["fund_id"] == f_id][ + "fund_name"].values[0] return_rank_evaluate = return_rank_evaluate + name + "ã€" - return_rank_evaluate = return_rank_evaluate[:-1] +"ç‰" + str(num) + "åªäº§å“稳å¥ï¼Œå¯¹ç»„åˆçš„æ”¶ç›ŠçŽ‡è´¡çŒ®æ˜Žæ˜¾ï¼Œ" + return_rank_evaluate = return_rank_evaluate[:-1] + "ç‰" + str(num) + "åªäº§å“稳å¥ï¼Œå¯¹ç»„åˆçš„æ”¶ç›ŠçŽ‡è´¡çŒ®æ˜Žæ˜¾ï¼Œ" # æ£æ”¶ç›ŠåŸºé‡‘æ•°é‡ group_hold_data = pd.DataFrame(group_result[group_name]["group_hoding_info"]) @@ -585,28 +586,29 @@ class PortfolioDiagnose(object): else: no_data_fund_evaluate = "ï¼›" - group_order_df = data_adaptor.user_customer_order_df[data_adaptor.user_customer_order_df["folio_name"] == group_name] + group_order_df = data_adaptor.user_customer_order_df[ + data_adaptor.user_customer_order_df["folio_name"] == group_name] strategy_list = group_order_df["substrategy"] uniqe_strategy = list(strategy_list.unique()) uniqe_strategy_name = [dict_substrategy[int(x)] + "ã€" for x in uniqe_strategy] # 覆盖的基金åç§° strategy_name_evaluate = "".join(uniqe_strategy_name)[:-1] - - if len(uniqe_strategy)/float(len(strategy_list)) > 0.6: + if len(uniqe_strategy) / float(len(strategy_list)) > 0.6: strategy_distribution_evaluate = "ç–略上有一定分散" else: strategy_distribution_evaluate = "ç–略分散程度ä¸é«˜" # 相关性 if len(self.abandon_fund_corr) > 0: - fund_corr_name = [str(group_order_df[group_order_df["fund_id"] == f_id]["fund_name"].values[0]) + "å’Œ" for f_id in self.abandon_fund_corr] + fund_corr_name = [str(group_order_df[group_order_df["fund_id"] == f_id]["fund_name"].values[0]) + "å’Œ" for + f_id in self.abandon_fund_corr] fund_corr_evaluate = "".join(fund_corr_name)[:-1] + "相关性较高,建议调整组åˆé…比;" else: fund_corr_evaluate = "ï¼›" num_fund = len(self.portfolio) evaluate_enum = [["优秀", "良好", "一般"], - ["优秀", "良好", "åˆæ ¼", "较差"]] + ["优秀", "良好", "åˆæ ¼", "较差"]] z_score_evaluate = evaluate_enum[0][z_score_level] drawdown_evaluate = evaluate_enum[1][drawdown_level] @@ -759,23 +761,30 @@ class PortfolioDiagnose(object): indicator_compare = [new_indicator, odl_indicator] - sentence = "在ä¿ç•™{}的基础上,建议赎回{},并增é…{}åŽï¼Œæ•´ä½“ç»„åˆæ³¢åŠ¨çŽ‡å¤§å¹…é™ä½Žï¼Œæœ€å¤§å›žæ’¤ä»Ž{}é™åˆ°ä¸è¶³{},年化收益率æå‡{}个点" - hold_fund = set(self.portfolio) - set(self.abandon_fund_score + self.abandon_fund_corr) + # 在ä¿ç•™{}的基础上,建议赎回{},并增é…{}åŽï¼Œæ•´ä½“ç»„åˆæ³¢åŠ¨çŽ‡å¤§å¹…é™ä½Žï¼Œæœ€å¤§å›žæ’¤ä»Ž{}é™åˆ°ä¸è¶³{},年化收益率æå‡{}个点 + 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] 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] proposal_fund = self.proposal_fund + proposal_fund_name = [get_fund_name(x).values[0][0] for x in proposal_fund] - sentence= "" + sentence = [] if hold_fund is not None: - sentence.join("在ä¿ç•™".join(hold_fund) + "的基础上,") + sentence.append("在ä¿ç•™" + "".join([i + "," for i in hold_fund_name]).rstrip(",") + "的基础上") if abandon_fund is not None: - sentence.join("建议赎回".join(abandon_fund) + ",") + sentence.append("建议赎回" + "".join([i + "," for i in abandon_fund_name]).rstrip(",")) if proposal_fund is not None: - sentence.join("增é…".join(proposal_fund) + "åŽï¼Œ") + sentence.append("增é…" + "".join([i + "," for i in proposal_fund_name]).rstrip(",") + "åŽ") + if new_volatility < old_volatility * 0.9: + sentence.append("æ•´ä½“ç»„åˆæ³¢åŠ¨çŽ‡å¤§å¹…é™ä½Ž") if new_drawdown < old_max_drawdown: - sentence.join("æ•´ä½“ç»„åˆæ³¢åŠ¨çŽ‡å¤§å¹…é™ä½Žï¼Œ") + sentence.append("最大回撤从{:.2%}é™åˆ°ä¸è¶³{:.2%}".format(old_max_drawdown[0], new_drawdown[0])) if new_return_ratio_year > old_return_ratio_year: - sentence.join("年化收益率æå‡{}个点。".format(round(new_return_ratio_year-old_return_ratio_year, 1))) - return suggestions_result, suggestions_result_asset, return_compare_result, indicator_compare, sentence + sentence.append("年化收益率æå‡{:.2f}个点".format((new_return_ratio_year - old_return_ratio_year) * 100)) + + whole_sentence = ",".join(sentence).lstrip(",") + "。" + return suggestions_result, suggestions_result_asset, whole_sentence def single_evaluation(self, fund_id): """ diff --git a/app/utils/fund_rank.py b/app/utils/fund_rank.py index df51c497b12fdf01d5b35cef42f98236e2876775..98f9fb703296ed302b494bc7ada0888b33cf61b4 100644 --- a/app/utils/fund_rank.py +++ b/app/utils/fund_rank.py @@ -1,7 +1,3 @@ - -from sqlalchemy import create_engine - - # db = create_engine( # 'mysql+pymysql://tamp_fund:@imeng408@tamper.mysql.polardb.rds.aliyuncs.com:3306/tamp_fund?charset=utf8mb4', # pool_size=50, @@ -43,7 +39,7 @@ def get_nav(fund, start_date, rollback=False, invest_type='public'): "WHERE ts_code='{}'".format(fund) cur = tamp_product_session.execute(sql) data = cur.fetchall() - df = pd.DataFrame(list(data), columns=['ts_code', 'end_date', 'adj_nav']).dropna(how='any') + df = pd.DataFrame(list(data), columns=['fund_id', 'end_date', 'adj_nav']).dropna(how='any') df.rename({'ts_code': 'fund_id'}, axis=1, inplace=True) else: sql = "SELECT fund_id, price_date, cumulative_nav FROM fund_nav " \