jinjia2html_v2.py 18.3 KB
import json
import sys
import time
import uuid

from jinja2 import PackageLoader, Environment

from app.api.engine import work_dir, pdf_folder, template_folder, pdf_save_folder
from app.config.default_template_params import hold_default_template, diagnose_default_template
from app.service.fund_service import fund_index_compare
from app.service.portfolio_diagnose import PortfolioDiagnose
from app.service.result_service_v2 import UserCustomerResultAdaptor
import numpy as np
from concurrent import futures
import os
from datetime import datetime

# 准备数据
from app.utils.draw import draw_month_return_chart, draw_contribution_chart, draw_combination_chart, \
    draw_old_combination_chart, draw_index_combination_chart
from app.utils.html_to_pdf import html_to_pdf
from app.utils.radar_chart import gen_radar_chart


class DataIntegrate:
    def __init__(self, ifa_id='USER_INFO15917850824287', customer_id='6716613802534121472', pdf_name=str(uuid.uuid4()) + '.pdf', type=1):
        self.user_customer = UserCustomerResultAdaptor(ifa_id, customer_id)
        self.customer_name = self.user_customer.customer_real_name
        self.ifa_name = self.user_customer.ifa_real_name
        # self.pdf_name = self.ifa_name + "_" + self.customer_name + "_" + '.pdf'
        self.pdf_name = pdf_name
        # 1持仓报告2诊断报告
        self.type = type
        # 全部数据
        self.df = self.user_customer.calculate_total_data()
        # 组合结果数据
        self.d = self.user_customer.calculate_group_result_data()

        self.all_folio_result = {}
        # 分组合拼接结果数据
        self.get_group_result()

        # 投资总览
        self.get_summarize()
        # 月度回报
        self.get_month_return()
        # 月度回报表格
        self.get_month_table_return()

    # 分组和计算个基点评以及新增基金等结果
    def get_group_result(self):
        for group_name, group_result in self.d.items():
            portfolio_diagnose = self.get_portfolio_diagnose(group_result["fund_id_list"], invest_amount=group_result["total_cost"])
            cur_group_portfolio_result = {
                'new_correlation': [],
                'propose_fund_data_list': [],
                'suggestions_result': {},
                'suggestions_result_asset': {},
                'return_compare_pic': [],
                'indicator_compare': [],
                'new_group_evaluation': [],
                "correlation": group_result["correlation"]
            }

            # 旧持仓组合点评
            self.comments_on_position_portfolio(portfolio_diagnose, group_name, cur_group_portfolio_result)
            # 贡献分解
            self.contribution_deco(group_result, cur_group_portfolio_result)
            # 目标与业绩
            self.objectives_performance(group_result, cur_group_portfolio_result)
            # 个基点评
            self.single_fund_comment(portfolio_diagnose, cur_group_portfolio_result)
            # 旧收益比较
            self.get_old_compare_pic(cur_group_portfolio_result)
            # 旧相关性
            self.get_old_correlation(portfolio_diagnose, cur_group_portfolio_result)
            if self.type == 2:
                # 新增基金
                self.propose_fund(portfolio_diagnose, cur_group_portfolio_result)
                # 新收益比较
                self.get_transfer_suggestions(portfolio_diagnose, group_name, cur_group_portfolio_result)
                # 新相关性
                self.get_new_correlation(portfolio_diagnose, 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):
        if invest_amount < 10000000:
            invest_amount = 10000000
        folio_fund_dict = {}
        for fd in portfolio:
            folio_fund_dict[fd] = self.user_customer.all_fund_type_dict[fd]
        portfolio_diagnose = PortfolioDiagnose(client_type=client_type, portfolio=folio_fund_dict,
                                               invest_amount=float(invest_amount),
                                               start_date=self.user_customer.start_date)
        if self.type == 2:
            portfolio_diagnose.optimize()
        return portfolio_diagnose

    # 全部数据综述结果
    def get_summarize(self):
        """投资总览."""
        self.total_cost = int(self.df["total_cost"])  # 投资成本
        self.now_yield = round((self.df['cumulative_return']-1)*100, 2)     # 成立以来累计收益率
        self.now_annualised_return = round(self.df["return_ratio_year"] * 100, 2)  # 年化收益率
        self.index_yield = round((self.df["index_result"]["return_ratio"]-1)*100, 2)    # 指数收益率
        self.now_withdrawal = round(self.df["max_drawdown"][0]*100, 2)  # 最大回撤
        self.index_withdrawal = round(self.df["index_result"]["max_drawdown"][0]*100, 2)    # 指数最大回撤
        self.now_month_income = int(self.df["cur_month_profit"])  # 本月收益
        self.month_rise = round(self.df["cur_month_profit_ratio"] * 100, 2)  # 本月涨幅
        self.year_totoal_rate_of_return = round(self.df["cur_year_profit_ratio"] * 100, 2)  # 今年累计收益率
        self.now_year_income = int(self.df["cur_year_profit"])  # 今年累计收益
        self.final_balance = int(self.df["total_cost"] + self.df["cumulative_profit"])  # 期末资产
        self.total_profit = int(self.df["cumulative_profit"])  # 累计盈利

    def get_month_return(self):
        """月度回报."""
        """组合月度及累计回报率曲线图"""
        xlabels, product_list, cumulative = self.user_customer.get_month_return_chart()
        self.monthly_return_performance_pic = draw_month_return_chart(xlabels, product_list, cumulative)

    def get_month_table_return(self):
        """月度盈亏和期末资产"""
        self.monthly_table_return = self.df["month_return_data_dict"]

    # 旧组合持仓点评,贡献分解数据
    def comments_on_position_portfolio(self, portfolio_diagnose, folio, cur_group_portfolio_result):
        """旧持仓组合点评. 旧贡献分解数据"""
        cur_group_portfolio_result["old_evaluation"], cur_group_portfolio_result["old_return_compare_data"],\
        cur_group_portfolio_result["old_indicator_compare"] = portfolio_diagnose.old_evaluation(folio, self.d, self.user_customer)

    def contribution_deco(self, group_result, cur_group_portfolio_result):
        """贡献分解."""
        g_data = group_result["contribution_decomposition"]
        cur_group_portfolio_result["contribution_decomposition"] = draw_contribution_chart(g_data['xlabels'], g_data['product_list'], g_data['cumulative'])

    def single_fund_comment(self, portfolio_diagnose, cur_group_portfolio_result):
        """个基点评."""
        single_fund_data_list = []
        portfolio_evaluation = portfolio_diagnose.old_portfolio_evaluation()
        index_compare_chart_data = portfolio_diagnose.original_fund_index_compare(self.user_customer.fund_cnav_total)
        # with futures.ProcessPoolExecutor(os.cpu_count()) as executor:
        #     res = executor.map(draw_index_combination_chart, index_compare_chart_data)
        # res = list(res)
        res = []
        for chart_data in index_compare_chart_data:
            r = draw_index_combination_chart(chart_data)
            res.append(r)

        for i in range(len(portfolio_evaluation)):
            single_fund_data_list.append({
                'fund_name': portfolio_evaluation[i]['name'],
                'status': portfolio_evaluation[i]['status'],
                'evaluation': portfolio_evaluation[i]['data'],
                'radar_chart_path': res[i]
            })
        cur_group_portfolio_result["single_fund_data_list"] = single_fund_data_list

    def get_old_compare_pic(self, cur_group_portfolio_result):
        """旧收益比较"""
        cur_group_portfolio_result["old_return_compare_pic"] = draw_old_combination_chart(cur_group_portfolio_result["old_return_compare_data"]["xlabels"],
                                                                                          cur_group_portfolio_result["old_return_compare_data"]["origin_combination"],
                                                                                          cur_group_portfolio_result["old_return_compare_data"]["index"])

    def get_transfer_suggestions(self, portfolio_diagnose, folio, cur_group_portfolio_result):
        """新收益比较,调仓建议"""
        cur_group_portfolio_result["suggestions_result"], cur_group_portfolio_result["suggestions_result_asset"], \
        cur_group_portfolio_result["return_compare_data"], \
        cur_group_portfolio_result["indicator_compare"], cur_group_portfolio_result["new_group_evaluation"] = portfolio_diagnose.new_evaluation(folio, self.d,
                                                                                                   self.user_customer)

        cur_group_portfolio_result["return_compare_pic"] = draw_combination_chart(cur_group_portfolio_result["return_compare_data"]["xlabels"],
                                                                                  cur_group_portfolio_result["return_compare_data"]["new_combination"],
                                                                                  cur_group_portfolio_result["return_compare_data"]["origin_combination"],
                                                                                  cur_group_portfolio_result["return_compare_data"]["index"])

    def get_old_correlation(self, portfolio_diagnose, cur_group_portfolio_result):
        """旧相关性分析."""
        old_correlation = cur_group_portfolio_result["correlation"]
        old_correlation_columns = old_correlation.columns.tolist()
        old_correlation_values = old_correlation.values.tolist()
        cur_group_portfolio_result["old_correlation"] = list(zip(range(1, len(old_correlation_columns)+1), old_correlation_columns, old_correlation_values))
        del cur_group_portfolio_result["correlation"]

    def get_new_correlation(self, portfolio_diagnose, cur_group_portfolio_result):
        """新相关性分析."""
        new_correlation = portfolio_diagnose.new_correlation
        new_correlation_columns = new_correlation.columns.tolist()
        new_correlation_values = new_correlation.values.tolist()
        cur_group_portfolio_result["new_correlation"] = list(zip(range(1, len(new_correlation_columns)+1), new_correlation_columns, new_correlation_values))

    def propose_fund(self, portfolio_diagnose, cur_group_portfolio_result):
        """新增基金"""
        # 优化组合建议1 -- 新增基金
        propose_fund_data_list = []
        propose_fund_evaluation = portfolio_diagnose.propose_fund_evaluation()
        # propose_radar_chart_data = portfolio_diagnose.propose_fund_radar()
        # with futures.ProcessPoolExecutor(os.cpu_count()) as executor:
        #     res = executor.map(gen_radar_chart, propose_radar_chart_data)
        res = []
        for fund_id in portfolio_diagnose.propose_portfolio.columns:
            r = fund_index_compare(fund_id, portfolio_diagnose.portfolio_dict.get(fund_id, 2))
            res.append(r)
        for i in range(len(propose_fund_evaluation)):
            propose_fund_data_list.append({
                'fund_name': propose_fund_evaluation[i]['name'],
                'status': '增仓',
                'evaluation': propose_fund_evaluation[i]['data'],
                'radar_chart_path': res[i]
            })
        cur_group_portfolio_result["propose_fund_data_list"] = propose_fund_data_list

    def objectives_performance(self, group_result, cur_group_portfolio_result):
        """目标与业绩"""

        cur_group_portfolio_result["totoal_rate_of_return"] = "%.2f" % round((group_result['cumulative_return']-1)*100, 2)       # 成立以来累计收益率
        cur_group_portfolio_result["annualised_return"] = "%.2f" % round(group_result["return_ratio_year"]*100, 2)     # 年化收益率
        cur_group_portfolio_result["volatility"] = "%.2f" % round(group_result["volatility"]*100, 2)
        cur_group_portfolio_result["max_withdrawal"] = "%.2f" % round(group_result["max_drawdown"][0]*100, 2)
        cur_group_portfolio_result["sharpe_ratio"] = "%.2f" % round(group_result["sharpe"], 2)
        cur_group_portfolio_result["cost_of_investment"] = "%.2f" % round(group_result["total_cost"]/10000.0, 2)    # 投资成本
        cur_group_portfolio_result["index_section_return"] = "%.2f" % round((group_result["index_result"]["return_ratio"]-1)*100, 2)
        cur_group_portfolio_result["index_annualised_return"] = "%.2f" % round(group_result["index_result"]["return_ratio_year"]*100, 2)     # 年化收益率
        cur_group_portfolio_result["index_volatility"] = "%.2f" % round(group_result["index_result"]["volatility"]*100, 2)
        cur_group_portfolio_result["index_max_withdrawal"] = "%.2f" % round(group_result["index_result"]["max_drawdown"][0]*100, 2)
        cur_group_portfolio_result["index_sharpe_ratio"] = "%.2f" % round(group_result["index_result"]["sharpe"], 2)

        cur_group_portfolio_result["group_nav_info"] = group_result["group_nav_info"]
        cur_group_portfolio_result["group_hoding_info"] = group_result["group_hoding_info"]
        cur_group_portfolio_result["group_hoding_info_total"] = group_result["group_hoding_info_total"]

    def get_template_data(self, default_template=None):
        """"""
        if self.type == 1:
            # 持仓报告数据
            data = {
                # 全局数据
                'customer_name': self.customer_name,
                'year_month': datetime.now().strftime("%Y-%m-%d"),
                'valueSex': self.user_customer.valueSex,
                'month': self.user_customer.month_start_date.strftime("%m"),
                'start_date': self.user_customer.start_date.strftime("%Y-%m-%d"),
                'latest_worth_day': self.user_customer.last_nav_date,
                'customer_level': '平衡型',
                # 综述数据
                'now_allocation_amount': '{:,}'.format(self.total_cost), 'now_yield': self.now_yield,
                'index_yield': self.index_yield,
                'now_annualised_return': self.now_annualised_return,
                'now_withdrawal': self.now_withdrawal, 'index_withdrawal': self.index_withdrawal,
                'expected_withdrawal': 20,
                'now_year_income': '{:,}'.format(self.now_year_income),
                'now_month_income': '{:,}'.format(self.now_month_income),
                'final_balance': '{:,}'.format(self.final_balance), 'total_profit': '{:,}'.format(self.total_profit),
                'total_profit_temp': self.total_profit,
                'now_year_income_temp': self.now_year_income, 'now_month_income_temp': self.now_month_income,

                'monthly_return_performance_pic': self.monthly_return_performance_pic,
                'month_rise': self.month_rise, 'year_totoal_rate_of_return': self.year_totoal_rate_of_return,
                'monthly_table_return': self.monthly_table_return,

                # 组合数据
                'all_folio_result': self.all_folio_result,

            }
            if default_template:
                self.data = {**default_template, **data}
            else:
                self.data = {**hold_default_template, **data}
        elif self.type == 2:
            # 诊断报告数据
            data = {
                # 全局数据
                'customer_name': self.customer_name,
                'year_month': self.user_customer.end_date.strftime("%Y-%m-%d"),
                'valueSex': self.user_customer.valueSex,
                'month': self.user_customer.month_start_date.strftime("%m"),
                'start_date': self.user_customer.start_date.strftime("%Y-%m-%d"),
                'latest_worth_day': self.user_customer.last_nav_date,
                'customer_level': '平衡型',
                # 综述数据
                'now_allocation_amount': '{:,}'.format(self.total_cost), 'now_yield': self.now_yield,
                'index_yield': self.index_yield,
                'now_annualised_return': self.now_annualised_return,
                'now_withdrawal': self.now_withdrawal, 'index_withdrawal': self.index_withdrawal,
                'expected_withdrawal': 20,
                'now_year_income': '{:,}'.format(self.now_year_income),
                'now_month_income': '{:,}'.format(self.now_month_income),
                'final_balance': '{:,}'.format(self.final_balance), 'total_profit': '{:,}'.format(self.total_profit),
                'total_profit_temp': self.total_profit,
                'now_year_income_temp': self.now_year_income, 'now_month_income_temp': self.now_month_income,

                'monthly_return_performance_pic': self.monthly_return_performance_pic,
                'month_rise': self.month_rise, 'year_totoal_rate_of_return': self.year_totoal_rate_of_return,
                'monthly_table_return': self.monthly_table_return,

                # 组合数据
                'all_folio_result': self.all_folio_result,
            }
            if default_template:
                self.data = {**default_template, **data}
            else:
                self.data = {**hold_default_template, **data}
        return self.data

    def render_data(self, data=None):
        # 全部数据
        if data:
            self.data = data
        # 开始渲染html模板
        env = Environment(loader=PackageLoader('app', 'templates'))  # 创建一个包加载器对象
        # template = env.get_template('monthReport.html')  # 获取一个模板文件
        template = env.get_template('/v2/monthReportV2.1.html')  # 获取一个模板文件
        monthReport_html = template.render(self.data).replace('None', 'none')  # 渲染
        # 保存 monthReport_html
        # save_file = "app/pdf/monthReport.html"
        # with open(save_file, 'w', encoding="utf-8") as f:
        #     f.write(monthReport_html)

        # save_file = "app/html/v2/monthReportV2.html"
        # with open(save_file, 'w', encoding="utf-8") as f:
        #     f.write(monthReport_html)
        html_to_pdf(monthReport_html, pdf_save_folder + self.pdf_name)


if __name__ == '__main__':
    start = time.time()
    dt = DataIntegrate(ifa_id='USER_INFO15955928945523', customer_id='67347292618078412802', type=2)
    data = dt.get_template_data()
    dt.render_data()
    print('耗时{}秒'.format(round(time.time()-start, 2)))