#!/usr/bin/python3.6 # -*- coding: utf-8 -*- # @Time : 2020/11/23 15:29 # @Author : Jie. Z # @Email : zhaojiestudy@163.com # @File : result_service.py # @Software: PyCharm import pandas as pd import numpy as np import datetime from decimal import Decimal from app.service.data_service import UserCustomerDataAdaptor from app.utils.week_evaluation import * class UserCustomerResultAdaptor(UserCustomerDataAdaptor): total_result_data = {} group_result_data = {} def __init__(self, user_id, customer_id, end_date=str(datetime.date.today())): super().__init__(user_id, customer_id, end_date) # 组合结果数据 def calculate_group_result_data(self): for folio in self.group_data.keys(): folio_report_data = {} cur_folio_result_cnav_data = self.group_data[folio]["result_cnav_data"] cur_folio_order_data = self.group_data[folio]["order_df"] freq_max = cur_folio_order_data["freq"].max() first_trade_date = cur_folio_order_data["confirm_share_date"].min() fund_id_list = list(cur_folio_order_data["fund_id"].unique()) fund_id_list_earn = [i + "_earn" for i in fund_id_list] # fund_id_list_amount = [i + "_amount" for i in fund_id_list] profit_df = cur_folio_result_cnav_data[fund_id_list_earn] # 组合收益率数组 # return_ratio_df, contribution_decomposition= self.combination_yield(cur_folio_result_cnav_data, fund_id_list) # resample_df = resample(return_ratio_df, self.trade_cal_date, freq_max) resample_cur_folio_result_cnav_data = resample(cur_folio_result_cnav_data, self.trade_cal_date, freq_max) 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) resample_df = resample(return_ratio_df, self.trade_cal_date, freq_max) resample_df = resample_df[resample_df.index <= self.end_date] # 收益分解df contribution_decomposition_df = contribution_decomposition.fillna(0)*100 p_plot_data = [] for a_fund_id in list(contribution_decomposition_df.columns): a_name = cur_folio_order_data[cur_folio_order_data["fund_id"]==a_fund_id]["fund_name"].values[0] plot_data = {'name': a_name, 'data': contribution_decomposition_df[a_fund_id].astype(np.float64).values} p_plot_data.append(plot_data) x_lables_data = list(contribution_decomposition_df.index) cumulative_data = {'name': '总收益', 'data': ((month_return_ratio_df["cum_return_ratio"] - 1)*100).values} folio_report_data["contribution_decomposition"] = {"xlabels": x_lables_data, "product_list": p_plot_data, "cumulative": cumulative_data} # 总成本 total_cost = float(cur_folio_order_data[cur_folio_order_data["order_type"] == 1]["confirm_amount"].sum() - \ cur_folio_order_data[cur_folio_order_data["order_type"] == 2]["confirm_amount"].sum()) folio_report_data["total_cost"] = total_cost # 累积盈利 cumulative_profit = profit_df.sum().sum() folio_report_data["cumulative_profit"] = float(cumulative_profit) # 区间年化收益率 n_freq = freq_days(int(freq_max)) return_ratio_year = annual_return((resample_df["cum_return_ratio"].values[-1]-1), resample_df, n_freq) folio_report_data["return_ratio_year"] = float(return_ratio_year) # 波动率 volatility_ = volatility(resample_df["cum_return_ratio"], n_freq) folio_report_data["volatility"] = float(volatility_) # 最大回撤 drawdown = max_drawdown(resample_df["cum_return_ratio"]) folio_report_data["max_drawdown"] = drawdown # 夏普比率 sim = simple_return(resample_df["cum_return_ratio"]) exc = excess_return(sim, BANK_RATE, n_freq) sharpe = sharpe_ratio(exc, sim, n_freq) folio_report_data["sharpe"] = float(sharpe) # 期末资产 ending_assets = cumulative_profit + total_cost folio_report_data["ending_assets"] = float(ending_assets) # 本月收益 cur_month_profit_df = profit_df.loc[self.month_start_date:self.end_date+datetime.timedelta(days=1), fund_id_list_earn] cur_month_profit = cur_month_profit_df.sum().sum() folio_report_data["cur_month_profit"] = float(cur_month_profit) # 本月累积收益率 last_profit_ratio = return_ratio_df.loc[:self.month_start_date, "cum_return_ratio"].values cur_profit_ratio = return_ratio_df.loc[self.month_start_date:, "cum_return_ratio"].values if len(last_profit_ratio) <= 0: cur_month_profit_ratio = cur_profit_ratio[-1] - 1 else: cur_month_profit_ratio = (cur_profit_ratio[-1] - last_profit_ratio[-1]) / last_profit_ratio[-1] folio_report_data["cur_month_profit_ratio"] = float(cur_month_profit_ratio) # 今年累积收益 cur_year_date = pd.to_datetime(str(datetime.date(year=self.end_date.year, month=1, day=1))) cur_year_profit_df = profit_df.loc[cur_year_date:self.end_date + datetime.timedelta(days=1), fund_id_list_earn] cur_year_profit = cur_year_profit_df.sum().sum() folio_report_data["cur_year_profit"] = float(cur_year_profit) # 今年累积收益率 last_profit_ratio = return_ratio_df.loc[:cur_year_date, "cum_return_ratio"].values cur_profit_ratio = return_ratio_df.loc[cur_year_date:, "cum_return_ratio"].values if len(last_profit_ratio) <= 0: cur_year_profit_ratio = cur_profit_ratio[-1] - 1 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) # 累积收益率 cumulative_return= return_ratio_df["cum_return_ratio"].values[-1] folio_report_data["cumulative_return"] = float(cumulative_return) # 月度分组 def year_month(x): a = x.year b = x.month return str(a) + "/" + str(b) profit_df_cp = profit_df.copy() profit_df_cp["date"] = profit_df_cp.index grouped = profit_df_cp.groupby(profit_df_cp["date"].apply(year_month)) sum_group = grouped.agg(np.sum) month_sum = sum_group.sum(axis=1) # 贡献分解 month_earn = sum_group.div(month_sum, axis='rows') month_earn["datetime"] = pd.to_datetime(month_earn.index) month_earn.sort_values(by="datetime", inplace=True) del month_earn["datetime"] col = list(month_earn.columns) col_ = {x: x.replace('_earn', '') for x in list(col)} month_earn.rename(columns=col_, inplace=True) # folio_report_data["contribution_decomposition"] = month_earn # 组合内单个基金净值数据 组合内基金持仓数据 result_fund_nav_info, result_fund_hoding_info = self.group_fund_basic_info_data(cur_folio_order_data, cur_folio_result_cnav_data, cumulative_profit, total_cost) # 拼接组合以及综合结果数据 folio_report_data["group_nav_info"] = result_fund_nav_info folio_report_data["group_hoding_info"] = result_fund_hoding_info # 对应指数数据 index_df = self.get_customer_index_nav_data() index_result = self.signal_fund_profit_result(index_df[index_df.index >= pd.to_datetime(first_trade_date)], "index") folio_report_data["index_result"] = index_result folio_report_data["return_df"] = resample_df self.group_result_data[folio] = folio_report_data return self.group_result_data # 综述数据 def calculate_total_data(self): report_data = {} cur_folio_result_cnav_data = self.total_customer_order_cnav_df cur_folio_order_data = self.user_customer_order_df freq_max = cur_folio_order_data["freq"].max() # fund_id_list = list(cur_folio_order_data["fund_id"].unique()) fund_id_list_earn = [i + "_earn" for i in fund_id_list] profit_df = cur_folio_result_cnav_data[fund_id_list_earn] # 持仓周期 first_trade_date = cur_folio_order_data["confirm_share_date"].min() hold_days = (self.end_date - pd.to_datetime(first_trade_date)).days report_data["hold_days"] = hold_days # 组合收益率数组 # return_ratio_df = self.combination_yield(cur_folio_result_cnav_data, fund_id_list) # resample_df = resample(return_ratio_df, self.trade_cal_date, freq_max) resample_cur_folio_result_cnav_data = resample(cur_folio_result_cnav_data, self.trade_cal_date, freq_max) 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) resample_df = resample(return_ratio_df, self.trade_cal_date, freq_max) resample_df = resample_df[resample_df.index <= self.end_date] # 总成本 total_cost = float(cur_folio_order_data[cur_folio_order_data["order_type"] == 1]["confirm_amount"].sum() - \ cur_folio_order_data[cur_folio_order_data["order_type"] == 2]["confirm_amount"].sum()) report_data["total_cost"] = total_cost # # # 累积盈利 # cumulative_profit = profit_df.sum().sum() # report_data["cumulative_profit"] = float(cumulative_profit) # # # 区间年化收益 # n_freq = freq_days(int(freq_max)) # return_ratio_year = annual_return((resample_df["cum_return_ratio"].values[-1] - 1), resample_df, n_freq) # report_data["return_ratio_year"] = float(return_ratio_year) # # # 波动率 # volatility_ = volatility(resample_df["cum_return_ratio"], n_freq) # report_data["volatility"] = float(volatility_) # 最大回撤 drawdown = max_drawdown(resample_df["cum_return_ratio"]) report_data["max_drawdown"] = drawdown # # # 夏普比率 # sim = simple_return(resample_df["cum_return_ratio"]) # exc = excess_return(sim, BANK_RATE, n_freq) # sharpe = sharpe_ratio(exc, sim, n_freq) # report_data["sharpe"] = float(sharpe) # # # 期末资产 # ending_assets = cumulative_profit + total_cost # report_data["ending_assets"] = float(ending_assets) # # # 本月收益 # cur_month_profit_df = profit_df.loc[self.month_start_date:self.end_date + datetime.timedelta(days=1), # fund_id_list_earn] # cur_month_profit = cur_month_profit_df.sum().sum() # report_data["cur_month_profit"] = float(cur_month_profit) # # # 本月累积收益率 # last_profit_ratio = return_ratio_df.loc[:self.month_start_date, "cum_return_ratio"].values # cur_profit_ratio = return_ratio_df.loc[self.month_start_date:, "cum_return_ratio"].values # if len(last_profit_ratio) <= 0: # cur_month_profit_ratio = cur_profit_ratio[-1] - 1 # else: # cur_month_profit_ratio = (cur_profit_ratio[-1] - last_profit_ratio[-1]) / last_profit_ratio[-1] # report_data["cur_month_profit_ratio"] = float(cur_month_profit_ratio) # # # 今年累积收益 # cur_year_date = pd.to_datetime(str(datetime.date(year=self.end_date.year, month=1, day=1))) # cur_year_profit_df = profit_df.loc[cur_year_date:self.end_date + datetime.timedelta(days=1), fund_id_list_earn] # cur_year_profit = cur_year_profit_df.sum().sum() # report_data["cur_year_profit"] = float(cur_year_profit) # # # 今年累积收益率 # last_profit_ratio = return_ratio_df.loc[:cur_year_date, "cum_return_ratio"].values # cur_profit_ratio = return_ratio_df.loc[cur_year_date:, "cum_return_ratio"].values # if len(last_profit_ratio) <= 0: # cur_year_profit_ratio = cur_profit_ratio[-1] - 1 # 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) # 月度回报 def year_month(x): a = x.year b = x.month return str(a) + "/" + str(b) profit_df_cp = profit_df.copy() profit_df_cp["date"] = profit_df_cp.index grouped = profit_df_cp.groupby(profit_df_cp["date"].apply(year_month)) sum_group = grouped.agg(np.sum) month_sum = sum_group.sum(axis=1) return_ratio_df["date"] = return_ratio_df.index return_group = return_ratio_df.groupby(return_ratio_df["date"].apply(year_month)) month_last_return_ratio = return_group.last()["cum_return_ratio"] month_result = pd.DataFrame({"date": month_sum.index, "profit": month_sum.values, "ratio": month_last_return_ratio.values}) month_result["datetime"] = pd.to_datetime(month_result["date"]) month_result.sort_values(by="datetime", inplace=True) report_data["month_return"] = month_result # # 贡献分解 # month_earn = sum_group.div(month_sum, axis='rows') # report_data["contribution_decomposition"] = month_earn # 累积收益率 cumulative_return = return_ratio_df["cum_return_ratio"].values[-1] report_data["cumulative_return"] = float(cumulative_return) # 对应指数数据 index_df = self.get_customer_index_nav_data() index_result = self.signal_fund_profit_result(index_df[index_df.index >= pd.to_datetime(first_trade_date)], "index") report_data["index_result"] = index_result self.total_result_data = report_data return report_data # 基金净值数据,持仓数据 def group_fund_basic_info_data(self, p_order_df, p_result_cnav_data, p_sum_profit, p_total_amount): group_fund_basic_info = [] group_fund_hoding_info = [] for index, row in p_order_df.iterrows(): cur_fund_id = str(row["fund_id"]) cur_fund_performance = self.all_fund_performance[cur_fund_id] cur_fund_info_series = cur_fund_performance.iloc[-1] # 基金净值数据 fund_basic_info = {"fund_name": row["fund_name"], "confirm_nav": round(row["nav"],4)} fund_basic_info["cur_nav"] = round(float(self.fund_nav_total[cur_fund_id].dropna().values[-1]), 4) fund_basic_info["cur_cnav"] = round(float(self.fund_cnav_total[cur_fund_id].dropna().values[-1]), 4) fund_basic_info["ret_1w"] = round(cur_fund_info_series["ret_1w"]*100, 2) if cur_fund_info_series["ret_1w"] is not None else "-" # 上周 fund_basic_info["ret_cum_1m"] = round(cur_fund_info_series["ret_cum_1m"]*100, 2) if cur_fund_info_series["ret_cum_1m"] is not None else "-" # 最近一个月 fund_basic_info["ret_cum_6m"] = round(cur_fund_info_series["ret_cum_6m"]*100, 2) if cur_fund_info_series["ret_cum_6m"] is not None else "-" # 最近半年 fund_basic_info["ret_cum_1y"] = round(cur_fund_info_series["ret_cum_1y"]*100, 2) if cur_fund_info_series["ret_cum_1y"] is not None else "-" # 最近一年 fund_basic_info["ret_cum_ytd"] = round(cur_fund_info_series["ret_cum_ytd"]*100, 2) if cur_fund_info_series["ret_cum_ytd"] is not None else "-" # 今年以来 fund_basic_info["ret_cum_incep"] = round(cur_fund_info_series["ret_cum_incep"]*100, 2) if cur_fund_info_series["ret_cum_incep"] is not None else "-" # 成立以来 # 申购以来 confirm_date = pd.to_datetime(row["confirm_share_date"]) confirm_cnav = float(p_result_cnav_data.loc[confirm_date, cur_fund_id]) fund_basic_info["ret_after_confirm"] = round((fund_basic_info["cur_cnav"] - confirm_cnav)/confirm_cnav*100, 2) # 分红 distribution_df = self.all_fund_distribution[cur_fund_id] if distribution_df.empty: fund_basic_info["distribution"] = "-" else: distribution_df["price_date"] = pd.to_datetime(distribution_df["price_date"]) distribution = float(distribution_df[distribution_df["price_date"] > confirm_date]["distribution"].sum()) fund_basic_info["distribution"] = round(distribution, 4) if distribution != 0 else "-" group_fund_basic_info.append(fund_basic_info) # 基金持仓数据 fund_hoding_info = {"fund_strategy_name": dict_substrategy[int(row["substrategy"])], "fund_name": row["fund_name"]} fund_hoding_info["confirm_date"] = row["confirm_share_date"] fund_hoding_info["weight"] = round(float(row["confirm_amount"]) / p_total_amount * 100, 2) fund_hoding_info["market_values"] = round((float(row["confirm_share"]) * (fund_basic_info["cur_cnav"] - confirm_cnav) + float(row["confirm_amount"]))/10000, 2) fund_hoding_info["cost"] = round(float(row["confirm_amount"])/10000, 2) fund_hoding_info["profit"] = round(float(row["confirm_share"]) * (fund_basic_info["cur_cnav"] - confirm_cnav)/10000, 2) # fund_hoding_info["ykb"] = fund_hoding_info["profit"] / fund_hoding_info["cost"] try: fund_hoding_info["ykb"] = round(float(gain_loss_ratio(p_result_cnav_data[cur_fund_id + "_profit"].dropna()))*100, 2) except: fund_hoding_info["ykb"] = "-" fund_hoding_info["profit_contribution"] = round(fund_hoding_info["profit"]*10000 / p_sum_profit*100, 2) group_fund_hoding_info.append(fund_hoding_info) return group_fund_basic_info, group_fund_hoding_info @staticmethod def combination_yield(p_combina_df, fund_id_list): fund_id_list_amount = [i + "_net_amount" for i in fund_id_list] fund_id_list_profit_ratio = [i + "_profit_ratio" for i in fund_id_list] nav_net_amount_df = p_combina_df[fund_id_list + fund_id_list_amount+fund_id_list_profit_ratio].copy() # nav_net_amount_df = resample(return_ratio_df, self.trade_cal_date, freq_max) nav_net_amount_df["sum_net_amount"] = nav_net_amount_df[fund_id_list_amount].sum(axis=1).apply(lambda x: Decimal.from_float(x)) for amount_name in fund_id_list: nav_net_amount_df[amount_name+"_amount_ratio"] = nav_net_amount_df[amount_name+"_net_amount"]/(nav_net_amount_df["sum_net_amount"]) nav_net_amount_df[amount_name+"_profit_ratio_weight"] = nav_net_amount_df[amount_name+"_amount_ratio"].shift(1) * nav_net_amount_df[amount_name+"_profit_ratio"] fund_profit_ratio = nav_net_amount_df[amount_name + "_profit_ratio"].dropna() + 1 nav_net_amount_df[amount_name + "_profit_cum_ratio_weight"] = (fund_profit_ratio.cumprod()-1)*nav_net_amount_df[amount_name+"_amount_ratio"].shift(1) # enter_date = nav_net_amount_df[amount_name+"_profit_ratio"].dropna() fund_id_list_profit_ratio_weight = [i + "_profit_ratio_weight" for i in fund_id_list] nav_profit_ratio_weight = nav_net_amount_df[fund_id_list_profit_ratio_weight].copy().fillna(method='ffill') # 组合收益率 return_ratio = nav_profit_ratio_weight.sum(axis=1) # 组合累积收益率 # return_ratio_list = list(return_ratio.values) cum_return_ratio = (return_ratio + 1).fillna(0).cumprod() # 收益率df cum_return_ratio_df = pd.DataFrame(return_ratio.values, columns=["return_ratio"]) cum_return_ratio_df["cum_return_ratio"] = cum_return_ratio.values cum_return_ratio_df.index = return_ratio.index # 单个基金累计收益分解df weight_name_list = [i + "_profit_cum_ratio_weight" for i in fund_id_list] signal_fund_cum_weight = nav_net_amount_df[weight_name_list] re_name = {x: x.replace("_profit_cum_ratio_weight", "") for x in weight_name_list} signal_fund_cum_weight.rename(columns=re_name, inplace=True) # 月度分组 def year_month(x): a = x.year b = x.month return str(a) + "/" + str(b) profit_df_cp = signal_fund_cum_weight.copy() profit_df_cp["date"] = list(profit_df_cp.index) grouped = profit_df_cp.groupby(profit_df_cp["date"].apply(year_month)) month_signal_fund_cum = grouped.last() month_signal_fund_cum.rename(columns={"date": "datetime"}, inplace=True) month_signal_fund_cum.sort_values(by="datetime", inplace=True) del month_signal_fund_cum["datetime"] p_cum_df = cum_return_ratio_df.copy() p_cum_df["date"] = list(p_cum_df.index) cum_grouped = p_cum_df.groupby(p_cum_df["date"].apply(year_month)) month_fund_cum = cum_grouped.last() month_fund_cum.rename(columns={"date": "datetime"}, inplace=True) month_fund_cum.sort_values(by="datetime", inplace=True) del month_fund_cum["datetime"] return cum_return_ratio_df, month_fund_cum, month_signal_fund_cum @staticmethod def signal_fund_profit_result(p_fund_nav_df, cur_fund_id): result = {"fund_id": cur_fund_id} fund_nav_df = p_fund_nav_df.copy() profit = fund_nav_df[cur_fund_id].dropna() - fund_nav_df[cur_fund_id].dropna().shift(1) fund_nav_df[cur_fund_id + "_profit"] = profit fund_nav_df[cur_fund_id + "_profit_ratio"] = profit / fund_nav_df[cur_fund_id].dropna().shift(1) # 累积收益率 return_ratio_list = list(fund_nav_df[cur_fund_id + "_profit_ratio"].astype("float64").values) cum_return_ratio = [] last_ratio = 0 for i in range(len(return_ratio_list)): if i == 0: last_ratio = 1 + return_ratio_list[i] if str(return_ratio_list[0]) != 'nan' else 1 cum_return_ratio.append(last_ratio) continue cur_ratio = (1 + return_ratio_list[i]) * last_ratio cum_return_ratio.append(cur_ratio) last_ratio = cur_ratio fund_nav_df['cum_return_ratio'] = cum_return_ratio # 区间收益率 result["return_ratio"] = cum_return_ratio[-1] # 区间年化收益 n_freq = freq_days(1) return_ratio_year = annual_return((fund_nav_df["cum_return_ratio"].values[-1] - 1), fund_nav_df, n_freq) result["return_ratio_year"] = float(return_ratio_year) # 波动率 volatility_ = volatility(fund_nav_df["cum_return_ratio"], n_freq) result["volatility"] = float(volatility_) # 最大回撤 drawdown = max_drawdown(fund_nav_df["cum_return_ratio"]) result["max_drawdown"] = drawdown # 夏普比率 sim = simple_return(fund_nav_df["cum_return_ratio"]) exc = excess_return(sim, BANK_RATE, n_freq) sharpe = sharpe_ratio(exc, sim, n_freq) result["sharpe"] = float(sharpe) return result def get_month_return_chart(self): res = self.total_result_data["month_return"] xlabels = res["date"].values res["profit"] = res["profit"].apply(lambda x: round(x/100.0, 2)) res["ratio"] = res["ratio"].apply(lambda x: round((x-1)*100, 2)) product_list = {'name': '月度回报', 'data': res["profit"].values} cumulative = {'name': '累积收益', 'data': res["ratio"].values} return xlabels, [product_list], cumulative def get_total_basic_data(self): return self.total_result_data def get_group_data(self): return self.group_result_data