#!/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 * dict_substrategy = {1010: '主观多头', 1020: '股票多空', 1030: '量化多头', 2010: '宏观策略', 3010: '主观趋势', 3020: '主观套利', 3030: '量化趋势', 3040: '量化套利', 3050: 'CTA策略', 4010: '并购重组', 4020: '定向增发', 4030: '大宗交易', 4040: '事件驱动复合', 5010: '市场中性', 5020: '套利策略', 5030: '相对价值复合', 6010: '纯债策略', 6020: '强债策略', 6030: '债券策略', 7010: 'MOM', 7020: 'FOF', 8010: '主观多策略', 8020: '量化多策略', -1: '其他策略'} BANK_RATE = 0.015 def resample(df, trading_cal, freq): """对基金净值表进行粒度不同的重采样,并剔除不在交易日中的结果 Args: df ([DataFrame]): [原始基金净值表] trading_cal ([type]): [上交所交易日表] freq ([type]): [重采样频率: 1:工作日,2:周, 3:月, 4:半月, 5:季度] Returns: [DataFrame]: [重采样后剔除不在交易日历中的净值表和交易日历以净值日期为索引的合表] """ freq_dict = {1: 'B', 2: 'W-FRI', 3: 'M', 4: 'SM', 5: 'Q'} resample_freq = freq_dict[freq] # 按采样频率进行重采样并进行净值的前向填充 df = df.resample(rule=resample_freq).ffill() # 根据采样频率确定最大日期偏移量(保证偏移后的日期与重采样的日期在同一周,同一月,同一季度等) timeoffset_dict = {1: 1, 2: 5, 3: 30, 4: 15, 5: 120} timeoffetmax = timeoffset_dict[freq] # Dataframe不允许直接修改index,新建一份index的复制并转为list new_index = list(df.index) # 遍历重采样后的日期 for idx, date in enumerate(df.index): # 如果重采样后的日期不在交易日历中 if date not in trading_cal.index: # 对重采样后的日期进行偏移 for time_offset in range(1, timeoffetmax): # 如果偏移后的日期在交易日历中,保留偏移后的日期 if date - datetime.timedelta(days=time_offset) in trading_cal.index: new_index[idx] = date - datetime.timedelta(days=time_offset) # 任意一天满足立即退出循环 break # 更改净值表的日期索引为重采样后且在交易日内的日期 df.index = pd.Series(new_index) return df class UserCustomerResultAdaptor(UserCustomerDataAdaptor): total_result_data = {} group_result_data = {} def __init__(self, user_id, customer_id, end_date=str(datetime.date.today())): # super().__init__() 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() 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 = self.combination_yield(cur_folio_result_cnav_data, fund_id_list) resample_df = resample(return_ratio_df, self.trade_cal_date, freq_max) # 总成本 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_cose"] = 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["contribution_decomposition"] = float(cumulative_return) # 组合内单个基金净值数据 组合内基金持仓数据 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 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) # 总成本 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["contribution_decomposition"] = float(cumulative_return) 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": row["nav"]} fund_basic_info["cur_nav"] = float(self.fund_nav_total[cur_fund_id].dropna().values[-1]) fund_basic_info["cur_cnav"] = float(self.fund_cnav_total[cur_fund_id].dropna().values[-1]) fund_basic_info["ret_1w"] = cur_fund_info_series["ret_1w"] # 上周 fund_basic_info["ret_cum_1m"] = cur_fund_info_series["ret_cum_1m"] fund_basic_info["ret_cum_6m"] = cur_fund_info_series["ret_cum_6m"] fund_basic_info["ret_cum_1y"] = cur_fund_info_series["ret_cum_1y"] fund_basic_info["ret_cum_ytd"] = cur_fund_info_series["ret_cum_ytd"] fund_basic_info["ret_cum_incep"] = cur_fund_info_series["ret_cum_incep"] # 申购以来 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"] = (fund_basic_info["cur_cnav"] - confirm_cnav)/confirm_cnav # 分红 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"]) fund_basic_info["distribution"] = float(distribution_df[distribution_df["price_date"] > confirm_date]["distribution"].sum()) 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"] = float(row["confirm_amount"]) / p_total_amount fund_hoding_info["market_values"] = float(row["confirm_share"]) * (fund_basic_info["cur_cnav"] - confirm_cnav) + float(row["confirm_amount"]) fund_hoding_info["cost"] = float(row["confirm_amount"]) fund_hoding_info["profit"] = float(row["confirm_share"]) * (fund_basic_info["cur_cnav"] - confirm_cnav) # fund_hoding_info["ykb"] = fund_hoding_info["profit"] / fund_hoding_info["cost"] try: fund_hoding_info["ykb"] = float(gain_loss_ratio(p_result_cnav_data[cur_fund_id + "_profit"].dropna())) except: fund_hoding_info["ykb"] = 0 fund_hoding_info["profit_contribution"] = fund_hoding_info["profit"] / p_sum_profit 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 + "_amount" for i in fund_id_list] fund_id_list_profit_ratio = [i + "_profit_ratio" for i in fund_id_list] nav_amount_df = p_combina_df[fund_id_list + fund_id_list_amount+fund_id_list_profit_ratio].copy() nav_amount_df["sum_amount"] = nav_amount_df[fund_id_list_amount].sum(axis=1).apply(lambda x: Decimal.from_float(x)) for amount_name in fund_id_list: nav_amount_df[amount_name+"_amount_ratio"] = nav_amount_df[amount_name+"_amount"]/nav_amount_df["sum_amount"] nav_amount_df[amount_name+"_profit_ratio_weight"] = nav_amount_df[amount_name+"_amount_ratio"] * nav_amount_df[amount_name+"_profit_ratio"] fund_id_list_profit_ratio_weight = [i + "_profit_ratio_weight" for i in fund_id_list] nav_profit_ratio_weight = nav_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 = [] last_ratio = 0 for i in range(len(return_ratio_list)): if i == 0: last_ratio = 1 + return_ratio_list[i] 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 # 收益率 cum_return_ratio_df = pd.DataFrame(return_ratio.values, columns=["return_ratio"]) cum_return_ratio_df["cum_return_ratio"] = cum_return_ratio cum_return_ratio_df.index = return_ratio.index return cum_return_ratio_df @staticmethod def signal_fund_profit_result(): pass 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