# -*- encoding:utf-8 -*-
"""度量模块基础"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import functools
import logging

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

from ..ExtBu.empyrical import stats
from ..CoreBu import ABuEnv
from ..CoreBu.ABuEnv import EMarketDataFetchMode
from ..CoreBu.ABuFixes import six
from ..UtilBu import ABuDateUtil
from ..UtilBu import ABuStatsUtil, ABuScalerUtil
from ..UtilBu.ABuDTUtil import warnings_filter
from ..TradeBu.ABuKLManager import AbuKLManager
from ..TradeBu.ABuCapital import AbuCapital
from ..TradeBu import ABuTradeExecute


__author__ = '阿布'
__weixin__ = 'abu_quant'


def valid_check(func):
    """检测度量的输入是否正常，非正常显示info，正常继续执行被装饰方法"""

    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):
        if self.valid:
            return func(self, *args, **kwargs)
        else:
            logging.info('metrics input is invalid or zero order gen!')

    return wrapper


class AbuMetricsBase(object):
    """主要适配股票类型交易对象的回测结果度量"""

    @classmethod
    def show_general(cls, orders_pd, action_pd, capital, benchmark, returns_cmp=False,
                     only_info=False, only_show_returns=False, enable_stocks_full_rate_factor=False):
        """
        类方法，针对输入执行度量后执行主要度量可视化及度量结果信息输出
        :param orders_pd: 回测结果生成的交易订单构成的pd.DataFrame对象
        :param action_pd: 回测结果生成的交易行为构成的pd.DataFrame对象
        :param capital: 资金类AbuCapital实例化对象
        :param benchmark: 交易基准对象，AbuBenchmark实例对象
        :param returns_cmp: 是否只度量无资金管理的情况下总体情况
        :param only_info: 是否只显示文字度量结果，不显示图像
        :param only_show_returns: 透传plot_returns_cmp，默认False, True则只显示收益对比不显示其它可视化
        :param enable_stocks_full_rate_factor: 是否开启满仓乘数
        :return AbuMetricsBase实例化类型对象
        """
        metrics = cls(orders_pd, action_pd, capital, benchmark,
                      enable_stocks_full_rate_factor=enable_stocks_full_rate_factor)
        metrics.fit_metrics()
        if returns_cmp:
            metrics.plot_order_returns_cmp(only_info=only_info)
        else:
            metrics.plot_returns_cmp(only_info=only_info, only_show_returns=only_show_returns)
            if not only_show_returns:
                metrics.plot_sharp_volatility_cmp(only_info=only_info)
        return metrics

    def __init__(self, orders_pd, action_pd, capital, benchmark, enable_stocks_full_rate_factor=False, log=print):
        """
        :param orders_pd: 回测结果生成的交易订单构成的pd.DataFrame对象
        :param action_pd: 回测结果生成的交易行为构成的pd.DataFrame对象
        :param capital: 资金类AbuCapital实例化对象
        :param benchmark: 交易基准对象，AbuBenchmark实例对象
        :param enable_stocks_full_rate_factor: 是否开启满仓乘数
        """
        self.capital = capital
        self.orders_pd = orders_pd
        self.action_pd = action_pd
        self.benchmark = benchmark
        """
            满仓乘数，如果设置为True, 针对度量信息如收益等需要除self.stocks_full_rate_factor
        """
        self.enable_stocks_full_rate_factor = enable_stocks_full_rate_factor
        # 验证输入的回测数据是否可度量，便于valid_check装饰器工作
        self.valid = False
        if self.orders_pd is not None and self.capital is not None and 'capital_blance' in self.capital.capital_pd:
            self.valid = True
        # ipython notebook下使用logging.info
        self.log_func = log


    @valid_check
    def fit_metrics(self):
        """执行所有度量函数"""
        # TODO 根据ORDER数量大于一定阀值启动进度条
        # with AbuProgress(100, 0, label='metrics progress...') as pg:
        # pg.show(5)
        self._metrics_base_stats()
        # pg.show(50)
        self._metrics_sell_stats()
        # pg.show(80)
        self._metrics_action_stats()
        # pg.show(95)
        self._metrics_extend_stats()

    def fit_metrics_order(self):
        """对外接口，并非度量真实成交了的结果，只度量orders_pd，即不涉及资金的度量"""
        self._metrics_sell_stats()

    def _metrics_base_stats(self):
        """度量真实成交了的capital_pd，即涉及资金的度量"""
        # 平均资金利用率
        self.cash_utilization = 1 - (self.capital.capital_pd.cash_blance /
                                     self.capital.capital_pd.capital_blance).mean()

        # 默认不使用满仓乘数即stocks_full_rate_factor＝1
        self.stocks_full_rate_factor = 1
        if self.enable_stocks_full_rate_factor:
            # 计算满仓比例
            stocks_full_rate = (self.capital.capital_pd.stocks_blance / self.capital.capital_pd.capital_blance)
            # 避免除0
            stocks_full_rate[stocks_full_rate == 0] = 1
            # 倒数得到满仓乘数
            self.stocks_full_rate_factor = (1 / stocks_full_rate)

        # 收益数据
        self.benchmark_returns = np.round(self.benchmark.kl_pd.close.pct_change(), 3)
        # 如果enable_stocks_full_rate_factor 则 * self.stocks_full_rate_factor的意义为随时都是满仓
        self.algorithm_returns = np.round(self.capital.capital_pd['capital_blance'].pct_change(),
                                          3) * self.stocks_full_rate_factor

        # 收益cum数据
        # noinspection PyTypeChecker
        self.algorithm_cum_returns = stats.cum_returns(self.algorithm_returns)
        self.benchmark_cum_returns = stats.cum_returns(self.benchmark_returns)

        # 最后一日的cum return
        self.benchmark_period_returns = self.benchmark_cum_returns[-1]
        self.algorithm_period_returns = self.algorithm_cum_returns[-1]

        # 交易天数
        self.num_trading_days = len(self.benchmark_returns)

        # 年化收益
        self.algorithm_annualized_returns = \
            (ABuEnv.g_market_trade_year / self.num_trading_days) * self.algorithm_period_returns
        self.benchmark_annualized_returns = \
            (ABuEnv.g_market_trade_year / self.num_trading_days) * self.benchmark_period_returns

        # 策略平均收益
        # noinspection PyUnresolvedReferences
        self.mean_algorithm_returns = self.algorithm_returns.cumsum() / np.arange(1, self.num_trading_days + 1,
                                                                                  dtype=np.float64)
        # 波动率
        self.benchmark_volatility = stats.annual_volatility(self.benchmark_returns)
        # noinspection PyTypeChecker
        self.algorithm_volatility = stats.annual_volatility(self.algorithm_returns)

        # 夏普比率
        self.benchmark_sharpe = stats.sharpe_ratio(self.benchmark_returns)
        # noinspection PyTypeChecker
        self.algorithm_sharpe = stats.sharpe_ratio(self.algorithm_returns)

        # 信息比率
        # noinspection PyUnresolvedReferences
        self.information = stats.information_ratio(self.algorithm_returns.values, self.benchmark_returns.values)

        # 阿尔法, 贝塔
        # noinspection PyUnresolvedReferences
        self.alpha, self.beta = stats.alpha_beta_aligned(self.algorithm_returns.values, self.benchmark_returns.values)

        # 最大回撤
        # noinspection PyUnresolvedReferences
        self.max_drawdown = stats.max_drawdown(self.algorithm_returns.values)

    def _metrics_sell_stats(self):
        """并非度量真实成交了的结果，只度量orders_pd，即认为没有仓位管理和资金量限制前提下的表现"""

        # 根据order中的数据，计算盈利比例
        self.orders_pd['profit_cg'] = self.orders_pd['profit'] / (
            self.orders_pd['buy_price'] * self.orders_pd['buy_cnt'])
        # 为了显示方便及明显
        self.orders_pd['profit_cg_hunder'] = self.orders_pd['profit_cg'] * 100
        # 成交了的pd isin win or loss
        deal_pd = self.orders_pd[self.orders_pd['sell_type'].isin(['win', 'loss'])]
        # 卖出原因get_dummies进行离散化
        dumm_sell = pd.get_dummies(deal_pd.sell_type_extra)
        dumm_sell_t = dumm_sell.T
        # 为plot_sell_factors函数生成卖出生效因子分布
        self.dumm_sell_t_sum = dumm_sell_t.sum(axis=1)

        # 买入因子唯一名称get_dummies进行离散化
        dumm_buy = pd.get_dummies(deal_pd.buy_factor)
        dumm_buy = dumm_buy.T
        # 为plot_buy_factors函数生成卖出生效因子分布
        self.dumm_buy_t_sum = dumm_buy.sum(axis=1)

        self.orders_pd['buy_date'] = self.orders_pd['buy_date'].astype(int)
        self.orders_pd[self.orders_pd['result'] != 0]['sell_date'].astype(int, copy=False)
        # 因子的单子的持股时间长度计算
        self.orders_pd['keep_days'] = self.orders_pd.apply(lambda x:
                                                           ABuDateUtil.diff(x['buy_date'],
                                                                            ABuDateUtil.current_date_int()
                                                                            if x['result'] == 0 else x[
                                                                                'sell_date']),
                                                           axis=1)
        # 筛出已经成交了的单子
        self.order_has_ret = self.orders_pd[self.orders_pd['result'] != 0]

        # 筛出未成交的单子
        self.order_keep = self.orders_pd[self.orders_pd['result'] == 0]

        xt = self.order_has_ret.result.value_counts()
        # 计算胜率
        if xt.shape[0] == 2:
            win_rate = xt[1] / xt.sum()
        elif xt.shape[0] == 1:
            win_rate = xt.index[0]
        else:
            win_rate = 0
        self.win_rate = win_rate
        # 策略持股天数平均值
        self.keep_days_mean = self.orders_pd['keep_days'].mean()
        # 策略持股天数中位数
        self.keep_days_median = self.orders_pd['keep_days'].median()

        # 策略期望收益
        self.gains_mean = self.order_has_ret[self.order_has_ret['profit_cg'] > 0].profit_cg.mean()
        if np.isnan(self.gains_mean):
            self.gains_mean = 0.0
        # 策略期望亏损
        self.losses_mean = self.order_has_ret[self.order_has_ret['profit_cg'] < 0].profit_cg.mean()
        if np.isnan(self.losses_mean):
            self.losses_mean = 0.0

        # 忽略仓位控的前提下，即假设每一笔交易使用相同的资金，策略的总获利交易获利比例和
        profit_cg_win_sum = self.order_has_ret[self.order_has_ret['profit_cg'] > 0].profit.sum()
        # 忽略仓位控的前提下，即假设每一笔交易使用相同的资金，策略的总亏损交易亏损比例和
        profit_cg_loss_sum = self.order_has_ret[self.order_has_ret['profit_cg'] < 0].profit.sum()

        if profit_cg_win_sum * profit_cg_loss_sum == 0 and profit_cg_win_sum + profit_cg_loss_sum > 0:
            # 其中有一个是0的，要转换成一个最小统计单位计算盈亏比，否则不需要
            if profit_cg_win_sum == 0:
                profit_cg_win_sum = 0.01
            if profit_cg_loss_sum == 0:
                profit_cg_win_sum = 0.01

        #  忽略仓位控的前提下，计算盈亏比
        self.win_loss_profit_rate = 0 if profit_cg_loss_sum == 0 else -round(profit_cg_win_sum / profit_cg_loss_sum, 4)
        #  忽略仓位控的前提下，计算所有交易单的盈亏总会
        self.all_profit = self.order_has_ret['profit'].sum()

    def _metrics_action_stats(self):
        """度量真实成交了的action_pd 计算买入资金的分布平均性，及是否有良好的分布"""

        action_pd = self.action_pd
        # 只选生效的, 由于忽略非交易日, 大概有多出0.6的误差
        self.act_buy = action_pd[action_pd.action.isin(['buy']) & action_pd.deal.isin([True])]
        # drop重复的日期上的行为，只保留一个，cp_date形如下所示
        cp_date = self.act_buy['Date'].drop_duplicates()
        """
            cp_date
            0      20141024
            2      20141029
            20     20150127
            21     20150205
            23     20150213
            25     20150218
            31     20150310
            34     20150401
            36     20150409
            39     20150422
            41     20150423
            44     20150428
            58     20150609
            59     20150610
            63     20150624
            66     20150715
            67     20150717
        """
        dt_fmt = cp_date.apply(lambda order: ABuDateUtil.str_to_datetime(str(order), '%Y%m%d'))
        dt_fmt = dt_fmt.apply(lambda order: (order - dt_fmt.iloc[0]).days)
        # 前后两两生效交易时间相减
        self.diff_dt = dt_fmt - dt_fmt.shift(1)
        # 计算平均生效间隔时间
        self.effect_mean_day = self.diff_dt.mean()

        if self.act_buy.empty:
            self.act_buy['cost'] = 0
            self.cost_stats = 0
            self.buy_deal_rate = 0
        else:
            self.act_buy['cost'] = self.act_buy.apply(lambda order: order.Price * order.Cnt, axis=1)
            # 计算cost各种统计度量值
            self.cost_stats = ABuStatsUtil.stats_namedtuple(self.act_buy['cost'])

            buy_action_pd = action_pd[action_pd['action'] == 'buy']
            buy_action_pd_deal = buy_action_pd['deal']
            # 计算资金对应的成交比例
            self.buy_deal_rate = buy_action_pd_deal.sum() / buy_action_pd_deal.count()

    def _metrics_extend_stats(self):
        """子类可扩展的metrics方法，子类在此方法中可定义自己需要度量的值"""
        pass

    @valid_check
    @warnings_filter  # skip: statsmodels / nonparametric / kdetools.py:20
    def plot_order_returns_cmp(self, only_info=True):
        """非真实成交的度量，认为资金无限，无资金管理的情况下总体情况"""

        self.log_func('买入后卖出的交易数量:{}'.format(self.order_has_ret.shape[0]))
        self.log_func('买入后尚未卖出的交易数量:{}'.format(self.order_keep.shape[0]))
        self.log_func('胜率:{:.4f}%'.format(self.win_rate * 100))
        self.log_func('平均获利期望:{:.4f}%'.format(self.gains_mean * 100))
        self.log_func('平均亏损期望:{:.4f}%'.format(self.losses_mean * 100))
        self.log_func('盈亏比:{:.4f}'.format(self.win_loss_profit_rate))
        self.log_func('所有交易收益比例和:{:.4f} '.format(self.order_has_ret.profit_cg.sum()))
        self.log_func('所有交易总盈亏和:{:.4f} '.format(self.all_profit))

        if only_info:
            return
        # 无法与基准对比，只能表示取向
        self.order_has_ret.sort_values('buy_date')['profit_cg'].cumsum().plot(grid=True, title='profit_cg cumsum')
        plt.show()

    @valid_check
    def plot_returns_cmp(self, only_show_returns=False, only_info=False):
        """考虑资金情况下的度量，进行与benchmark的收益度量对比，收益趋势，资金变动可视化，以及其它度量信息"""

        self.log_func('买入后卖出的交易数量:{}'.format(self.order_has_ret.shape[0]))
        self.log_func('买入后尚未卖出的交易数量:{}'.format(self.order_keep.shape[0]))

        self.log_func('胜率:{:.4f}%'.format(self.win_rate * 100))

        self.log_func('平均获利期望:{:.4f}%'.format(self.gains_mean * 100))
        self.log_func('平均亏损期望:{:.4f}%'.format(self.losses_mean * 100))

        self.log_func('盈亏比:{:.4f}'.format(self.win_loss_profit_rate))

        self.log_func('策略收益: {:.4f}%'.format(self.algorithm_period_returns * 100))
        self.log_func('基准收益: {:.4f}%'.format(self.benchmark_period_returns * 100))
        self.log_func('策略年化收益: {:.4f}%'.format(self.algorithm_annualized_returns * 100))
        self.log_func('基准年化收益: {:.4f}%'.format(self.benchmark_annualized_returns * 100))

        self.log_func('策略买入成交比例:{:.4f}%'.format(self.buy_deal_rate * 100))
        self.log_func('策略资金利用率比例:{:.4f}%'.format(self.cash_utilization * 100))
        self.log_func('策略共执行{}个交易日'.format(self.num_trading_days))

        if only_info:
            return

        self.benchmark_cum_returns.plot()
        self.algorithm_cum_returns.plot()
        plt.legend(['benchmark returns', 'algorithm returns'], loc='best')
        plt.show()

        if only_show_returns:
            return
        sns.regplot(x=np.arange(0, len(self.algorithm_cum_returns)), y=self.algorithm_cum_returns.values)
        plt.show()
        sns.distplot(self.capital.capital_pd['capital_blance'], kde_kws={"lw": 3, "label": "capital blance kde"})
        plt.show()

    @valid_check
    def plot_sharp_volatility_cmp(self, only_info=False):
        """sharp，volatility的策略与基准对比可视化，以及alpha阿尔法，beta贝塔，Information信息比率等信息输出"""

        self.log_func('alpha阿尔法:{:.4f}'.format(self.alpha))
        self.log_func('beta贝塔:{:.4f}'.format(self.beta))
        self.log_func('Information信息比率:{:.4f}'.format(self.information))

        self.log_func('策略Sharpe夏普比率: {:.4f}'.format(self.algorithm_sharpe))
        self.log_func('基准Sharpe夏普比率: {:.4f}'.format(self.benchmark_sharpe))

        self.log_func('策略波动率Volatility: {:.4f}'.format(self.algorithm_volatility))
        self.log_func('基准波动率Volatility: {:.4f}'.format(self.benchmark_volatility))

        if only_info:
            return

        sharp_volatility = pd.DataFrame([[self.algorithm_sharpe, self.benchmark_sharpe],
                                         [self.algorithm_volatility, self.benchmark_volatility]])
        sharp_volatility.columns = ['algorithm', 'benchmark']
        sharp_volatility.index = ['sharpe', 'volatility']
        sharp_volatility.plot(kind='bar', alpha=0.5)
        _ = plt.setp(plt.gca().get_xticklabels(), rotation=30)

    @valid_check
    def plot_effect_mean_day(self):
        """可视化因子平均生效间隔时间"""

        self.log_func('因子平均生效间隔:{}'.format(self.effect_mean_day))

        ddvc = self.diff_dt.value_counts()
        ddvc_rt = ddvc / ddvc.sum()
        plt.figure(figsize=(6, 6))
        plt.axes([0.025, 0.025, 0.95, 0.95])
        x = ddvc_rt.values
        labels = ddvc_rt.index
        plt.pie(x, labels=labels, explode=x * 0.1)
        plt.title('factor diff effect day')
        plt.show()

    @valid_check
    def plot_action_buy_cost(self):
        """可视化开仓花费情况"""

        self.log_func('开仓花费情况: ')
        self.log_func(self.cost_stats)

        plt.title('action buy cost')
        bins = int(len(self.act_buy['cost']) / 10)
        bins = bins if bins > 0 else 10
        self.act_buy['cost'].plot(kind='hist', bins=bins)
        plt.show()

    @valid_check
    def plot_sell_factors(self):
        """可视化卖出生效因子分布"""
        self.log_func('卖出择时生效因子分布：')
        self.log_func(self.dumm_sell_t_sum)
        if self.dumm_sell_t_sum.shape[0] > 1:
            self.dumm_sell_t_sum.plot(kind='barh')
            plt.title('sell factors barh')
            plt.show()

    @valid_check
    def plot_buy_factors(self):
        """可视化买入生效因子分布"""
        self.log_func('买入择时生效因子分布：')
        self.log_func(self.dumm_buy_t_sum)

        if self.dumm_buy_t_sum.shape[0] > 1:
            self.dumm_buy_t_sum.plot(kind='barh')
            plt.title('buy factors barh')
            plt.show()

    @valid_check
    def plot_keep_days(self):
        """可视化策略持股天数"""

        self.log_func('策略持股天数平均数: {:.3f}'.format(self.keep_days_mean))
        self.log_func('策略持股天数中位数: {:.3f}'.format(self.keep_days_median))
        bins = int(self.orders_pd['keep_days'].shape[0] / 5)
        bins = bins if bins > 0 else 5
        self.orders_pd['keep_days'].plot(kind='hist', bins=bins)
        plt.show()

    @valid_check
    def plot_max_draw_down(self):
        """可视化最大回撤"""

        cb_earn = self.capital.capital_pd['capital_blance'] - self.capital.read_cash
        shift = cb_earn.shape[0]
        max_draw_down = {-1: -1}
        cap_pd_index = cb_earn.index.tolist()

        for sf in np.arange(1, shift):
            sub_val = cb_earn.iloc[sf]
            sf_val = cb_earn[:sf]
            sf_val = sf_val.drop_duplicates(keep='last')

            diff = sf_val.values - sub_val

            if diff.max() > list(six.itervalues(max_draw_down))[0]:
                st_ind = diff.argmax()
                st_ind = sf_val.index[st_ind]
                end_ind = cap_pd_index[sf]
                max_draw_down = {(st_ind, end_ind): diff.max()}

        down_rate = list(six.itervalues(max_draw_down))[0] / self.capital.capital_pd['capital_blance'].loc[
            list(six.iterkeys(max_draw_down))[0][0]]
        """
            截取开始交易部分
        """
        cb_earn = cb_earn.loc[cb_earn[cb_earn != 0].index[0]:]
        cb_earn.plot()
        plt.plot(list(six.iterkeys(max_draw_down))[0][0], cb_earn.loc[list(six.iterkeys(max_draw_down))[0][0]],
                 'ro', markersize=12,
                 markeredgewidth=1.5,
                 markerfacecolor='None', markeredgecolor='green')

        plt.plot(list(six.iterkeys(max_draw_down))[0][1], cb_earn.loc[list(six.iterkeys(max_draw_down))[0][1]],
                 'ro', markersize=12,
                 markeredgewidth=1.5,
                 markerfacecolor='None', markeredgecolor='red')

        plt.plot([list(six.iterkeys(max_draw_down))[0][0], list(six.iterkeys(max_draw_down))[0][1]],
                 [cb_earn.loc[list(six.iterkeys(max_draw_down))[0][0]],
                  cb_earn.loc[list(six.iterkeys(max_draw_down))[0][1]]], 'o-')
        plt.grid(True)
        plt.show()

        self.log_func('最大回撤: {:5f}'.format(down_rate))
        self.log_func('最大回测启始时间:{}, 结束时间{}, 共回测{:3f}'.format(
            ABuDateUtil.timestamp_to_str(list(six.iterkeys(max_draw_down))[0][0]),
            ABuDateUtil.timestamp_to_str(list(six.iterkeys(max_draw_down))[0][1]),
            list(six.itervalues(max_draw_down))[0]))

    @valid_check
    def transform_to_full_rate_factor(self, read_cash=-1, kl_pd_manager=None, n_process_kl=ABuEnv.g_cpu_cnt,
                                      show=True):
        if ABuEnv.g_data_fetch_mode != EMarketDataFetchMode.E_DATA_FETCH_FORCE_LOCAL:
            self.log_func('transform_to_full_rate_factor func must in E_DATA_FETCH_FORCE_LOCAL env!')
            return

        if not hasattr(self, 'full_rate_metrics'):
            if read_cash == -1:
                # 如果外部不设置资金数，设置一个亿为大资金数
                read_cash = 100000000

            target_symbols = list(set(self.orders_pd.symbol))
            # 重新以很大的资金初始化AbuCapital
            capital = AbuCapital(read_cash, self.benchmark,
                                 user_commission_dict=self.capital.commission.commission_dict)
            if kl_pd_manager is None:
                kl_pd_manager = AbuKLManager(self.benchmark, capital)
                # 一次性在主进程中执行多进程获取k线数据，全部放入kl_pd_manager中，内部启动n_process_kl个进程执行
                kl_pd_manager.batch_get_pick_time_kl_pd(target_symbols, n_process=n_process_kl)

            # noinspection PyUnresolvedReferences
            action_pd = self.action_pd.sort_values(['Date', 'action'])
            action_pd.index = np.arange(0, action_pd.shape[0])
            # 最后将所有的action作用在资金上，生成资金时序，及判断是否能买入
            ABuTradeExecute.apply_action_to_capital(capital, action_pd, kl_pd_manager)
            # 最终创建一个子AbuMetricsBase对象在内部，action_pd, capital使用新计算出来的，满仓乘数参数设置为True
            # noinspection PyAttributeOutsideInit
            self.full_rate_metrics = AbuMetricsBase(self.orders_pd, action_pd, capital, self.benchmark,
                                                    enable_stocks_full_rate_factor=True)
            self.full_rate_metrics.fit_metrics()
        if show:
            self.full_rate_metrics.plot_returns_cmp(only_show_returns=True)
        return self.full_rate_metrics


class MetricsDemo(AbuMetricsBase):
    """
        扩展自定义度量类示例

        eg:
            metrics = MetricsDemo(*abu_result_tuple)
            metrics.fit_metrics()
            metrics.plot_commission()
    """

    def _metrics_extend_stats(self):
        """
            子类可扩展的metrics方法，子类在此方法中可定义自己需要度量的值:
            本demo示例交易手续费和策略收益之间的度量对比
        """
        commission_df = self.capital.commission.commission_df
        commission_df['commission'] = commission_df.commission.astype(float)
        commission_df['cumsum'] = commission_df.commission.cumsum()
        """
            eg:
                type	date	symbol	commission	cumsum
            0	buy	20141024	usAAPL	19.04	19.04
            0	buy	20141024	usAAPL	19.04	38.08
            0	buy	20141029	usNOAH	92.17	130.25
            0	buy	20141029	usBIDU	7.81	138.06
            0	buy	20141029	usBIDU	7.81	145.87
            0	buy	20141029	usVIPS	60.95	206.82
        """
        # 讲date转换为index
        dates_pd = pd.to_datetime(commission_df.date)
        commission = pd.DataFrame(index=dates_pd)
        """
            eg: commission
            2014-10-24	19.04
            2014-10-24	38.08
            2014-10-29	130.25
            2014-10-29	138.06
            2014-10-29	145.87
            2014-10-29	206.82
            2014-11-03	265.82
            2014-11-11	360.73
        """
        commission['cum'] = commission_df['cumsum'].values
        self.commission_cum = commission['cum']
        self.commission_sum = self.commission_cum[-1]

    def plot_commission(self):
        """
            使用计算好的首先费cumsum序列和策略收益cumsum序列进行可视化对比
            可视化收益曲线和手续费曲线之前的关系
        """
        print('回测周期内手续费共: {:.2f}'.format(self.commission_sum))
        # 使用缩放scaler_xy将两条曲线缩放到同一个级别
        x, y = ABuScalerUtil.scaler_xy(self.commission_cum, self.algorithm_cum_returns, type_look='look_max',
                                       mean_how=True)
        x.plot(label='commission')
        y.plot(label='algorithm returns')
        plt.legend(loc=2)
        plt.show()
