Пример #1
0
    def __init__(self,
                 account,
                 benchmark_code='000300',
                 benchmark_type=MARKET_TYPE.INDEX_CN,
                 if_fq=True,
                 market_data=None,
                 auto_reload=False):
        """
        account: QA_Account类/QA_PortfolioView类
        benchmark_code: [str]对照参数代码
        benchmark_type: [QA.PARAM]对照参数的市场
        if_fq: [Bool]原account是否使用复权数据
        if_fq选项是@尧提出的,关于回测的时候成交价格问题(如果按不复权撮合 应该按不复权价格计算assets)
        """
        self.account = account
        self.benchmark_code = benchmark_code  # 默认沪深300
        self.benchmark_type = benchmark_type
        self.client = DATABASE.risk

        self.client.create_index([("account_cookie", ASCENDING),
                                  ("user_cookie", ASCENDING),
                                  ("portfolio_cookie", ASCENDING)],
                                 unique=True)
        if auto_reload:
            pass
        else:
            self.fetch = {
                MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv,
                MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv
            }
            if market_data == None:
                if self.account.market_type == MARKET_TYPE.STOCK_CN:
                    self.market_data = QA_fetch_stock_day_adv(
                        self.account.code, self.account.start_date,
                        self.account.end_date)
                elif self.account.market_type == MARKET_TYPE.FUTURE_CN:
                    self.market_data = QA_fetch_future_day_adv(
                        self.account.code, self.account.start_date,
                        self.account.end_date)
            else:
                self.market_data = market_data
            self.if_fq = if_fq
            if self.account.market_type == MARKET_TYPE.FUTURE_CN:
                self.if_fq = False  # 如果是期货, 默认设为FALSE

            if self.market_value is not None:
                self._assets = (
                    self.market_value.sum(axis=1) +
                    self.account.daily_cash.set_index('date').cash).fillna(
                        method='pad')
            else:
                self._assets = self.account.daily_cash.set_index(
                    'date').cash.fillna(method='pad')

            self.time_gap = QA_util_get_trade_gap(self.account.start_date,
                                                  self.account.end_date)
            self.init_cash = self.account.init_cash
            self.init_assets = self.account.init_assets
Пример #2
0
class QA_Risk():
    """QARISK 是一个风险插件

    需要加载一个account/portfolio类进来:
    需要有
    code,start_date,end_date,daily_cash,daily_hold

    TODO:
    资金利用率 反应资金的利用程度
    股票周转率 反应股票的持仓天数
    预期PNL/统计学PNL
    """
    def __init__(self,
                 account,
                 benchmark_code='000300',
                 benchmark_type=MARKET_TYPE.INDEX_CN,
                 if_fq=True,
                 market_data=None,
                 auto_reload=False):
        """
        account: QA_Account类/QA_PortfolioView类
        benchmark_code: [str]对照参数代码
        benchmark_type: [QA.PARAM]对照参数的市场
        if_fq: [Bool]原account是否使用复权数据
        if_fq选项是@尧提出的,关于回测的时候成交价格问题(如果按不复权撮合 应该按不复权价格计算assets)
        """
        self.account = account
        self.benchmark_code = benchmark_code  # 默认沪深300
        self.benchmark_type = benchmark_type
        self.client = DATABASE.risk

        self.client.create_index([("account_cookie", ASCENDING),
                                  ("user_cookie", ASCENDING),
                                  ("portfolio_cookie", ASCENDING)],
                                 unique=True)
        if auto_reload:
            pass
        else:
            self.fetch = {
                MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv,
                MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv
            }
            if market_data == None:
                if self.account.market_type == MARKET_TYPE.STOCK_CN:
                    self.market_data = QA_fetch_stock_day_adv(
                        self.account.code, self.account.start_date,
                        self.account.end_date)
                elif self.account.market_type == MARKET_TYPE.FUTURE_CN:
                    self.market_data = QA_fetch_future_day_adv(
                        self.account.code, self.account.start_date,
                        self.account.end_date)
            else:
                self.market_data = market_data
            self.if_fq = if_fq
            if self.account.market_type == MARKET_TYPE.FUTURE_CN:
                self.if_fq = False  # 如果是期货, 默认设为FALSE

            if self.market_value is not None:
                if self.account.market_type == MARKET_TYPE.FUTURE_CN and self.account.allow_margin == True:
                    print('margin!')
                    self._assets = (
                        self.account.daily_frozen +
                        self.account.daily_cash.set_index('date').cash
                    ).dropna()
                else:
                    self._assets = (
                        self.market_value.sum(axis=1) +
                        self.account.daily_cash.set_index('date').cash).fillna(
                            method='pad')
            else:
                self._assets = self.account.daily_cash.set_index(
                    'date').cash.fillna(method='pad')

            self.time_gap = QA_util_get_trade_gap(self.account.start_date,
                                                  self.account.end_date)
            self.init_cash = self.account.init_cash
            self.init_assets = self.account.init_assets

    def __repr__(self):
        return '< QA_RISK ANALYSIS ACCOUNT/PORTFOLIO >'

    def __call__(self):
        return pd.DataFrame([self.message])

    @property
    def total_timeindex(self):
        return self.account.trade_range

    @property
    def market_value(self):
        """每日每个股票持仓市值表

        Returns:
            pd.DataFrame -- 市值表
        """
        if self.account.daily_hold is not None:
            if self.if_fq:

                return (self.market_data.to_qfq().pivot('close').fillna(
                    method='ffill') *
                        self.account.daily_hold.apply(abs)).fillna(
                            method='ffill')
            else:
                return (
                    self.market_data.pivot('close').fillna(method='ffill') *
                    self.account.daily_hold.apply(abs)).fillna(method='ffill')
        else:
            return None

    @property
    def daily_market_value(self):
        """每日持仓总市值表

        Returns:
            pd.DataFrame -- 市值表
        """
        if self.market_value is not None:
            return self.market_value.sum(axis=1)
        else:
            return None

    @property
    def assets(self):
        x1 = self._assets.reset_index()
        return x1.assign(date=pd.to_datetime(x1.date)).set_index('date')[0]

    @property
    def max_dropback(self):
        """最大回撤
        """
        return round(
            float(
                max([(self.assets.iloc[idx] - self.assets.iloc[idx::].min()) /
                     self.assets.iloc[idx]
                     for idx in range(len(self.assets))])), 2)

    @property
    def total_commission(self):
        """总手续费
        """
        return float(
            -abs(round(self.account.history_table.commission.sum(), 2)))

    @property
    def total_tax(self):
        """总印花税

        """

        return float(-abs(round(self.account.history_table.tax.sum(), 2)))

    @property
    def profit_construct(self):
        """利润构成

        Returns:
            dict -- 利润构成表
        """

        return {
            'total_buyandsell':
            round(self.profit_money - self.total_commission - self.total_tax,
                  2),
            'total_tax':
            self.total_tax,
            'total_commission':
            self.total_commission,
            'total_profit':
            self.profit_money
        }

    @property
    def profit_money(self):
        """盈利额

        Returns:
            [type] -- [description]
        """

        return float(round(self.assets.iloc[-1] - self.assets.iloc[0], 2))

    @property
    def profit(self):
        """盈利率(百分比)

        Returns:
            [type] -- [description]
        """

        return round(float(self.calc_profit(self.assets)), 2)

    @property
    def profit_pct(self):
        """利润
        """
        return self.calc_profitpctchange(self.assets)

    @property
    def annualize_return(self):
        """年化收益

        Returns:
            [type] -- [description]
        """

        return round(
            float(self.calc_annualize_return(self.assets, self.time_gap)), 2)

    @property
    def volatility(self):
        """波动率

        Returns:
            [type] -- [description]
        """
        return round(float(self.profit_pct.std() * math.sqrt(250)), 2)

    @property
    def ir(self):
        return round(self.calc_IR(), 2)

    @property
    @lru_cache()
    def message(self):
        return {
            'account_cookie': self.account.account_cookie,
            'portfolio_cookie': self.account.portfolio_cookie,
            'user_cookie': self.account.user_cookie,
            'annualize_return': round(self.annualize_return, 2),
            'profit': round(self.profit, 2),
            'max_dropback': self.max_dropback,
            'time_gap': self.time_gap,
            'volatility': self.volatility,
            'benchmark_code': self.benchmark_code,
            'bm_annualizereturn': self.benchmark_annualize_return,
            'bm_profit': self.benchmark_profit,
            'beta': self.beta,
            'alpha': self.alpha,
            'sharpe': self.sharpe,
            'init_cash': "%0.2f" % (float(self.assets[0])),
            'last_assets': "%0.2f" % (float(self.assets.iloc[-1])),
            'total_tax': self.total_tax,
            'total_commission': self.total_commission,
            'profit_money': self.profit_money,
            'assets': list(self.assets),
            'benchmark_assets': list(self.benchmark_assets),
            'timeindex': self.account.trade_day,
            'totaltimeindex': self.total_timeindex,
            'ir': self.ir,
            'month_profit': self.month_assets_profit.to_dict()
            # 'init_assets': round(float(self.init_assets), 2),
            # 'last_assets': round(float(self.assets.iloc[-1]), 2)
        }

    @property
    def benchmark_data(self):
        """
        基准组合的行情数据(一般是组合,可以调整)
        """
        return self.fetch[self.benchmark_type](self.benchmark_code,
                                               self.account.start_date,
                                               self.account.end_date)

    @property
    def benchmark_assets(self):
        """
        基准组合的账户资产队列
        """
        return (self.benchmark_data.close /
                float(self.benchmark_data.close.iloc[0]) *
                float(self.assets[0]))

    @property
    def benchmark_profit(self):
        """
        基准组合的收益
        """
        return round(float(self.calc_profit(self.benchmark_assets)), 2)

    @property
    def benchmark_annualize_return(self):
        """基准组合的年化收益

        Returns:
            [type] -- [description]
        """

        return round(
            float(
                self.calc_annualize_return(self.benchmark_assets,
                                           self.time_gap)), 2)

    @property
    def benchmark_profitpct(self):
        """
        benchmark 基准组合的收益百分比计算
        """
        return self.calc_profitpctchange(self.benchmark_assets)

    @property
    def beta(self):
        """
        beta比率 组合的系统性风险
        """
        try:
            res = round(
                float(
                    self.calc_beta(self.profit_pct.dropna(),
                                   self.benchmark_profitpct.dropna())), 2)
        except:
            print('贝塔计算错误。。')
            res = 0

        return res

    @property
    def alpha(self):
        """
        alpha比率 与市场基准收益无关的超额收益率
        """
        return round(
            float(
                self.calc_alpha(self.annualize_return,
                                self.benchmark_annualize_return, self.beta,
                                0.05)), 2)

    @property
    def sharpe(self):
        """
        夏普比率

        """
        return round(
            float(
                self.calc_sharpe(self.annualize_return, self.volatility,
                                 0.05)), 2)

    @property
    def sortino(self):
        """
        索提诺比率 投资组合收益和下行风险比值

        """
        pass

    @property
    def calmar(self):
        """
        卡玛比率
        """
        pass

    def set_benchmark(self, code, market_type):
        self.benchmark_code = code
        self.benchmark_type = market_type

    def calc_annualize_return(self, assets, days):
        return round((float(assets.iloc[-1]) / float(assets.iloc[0]) - 1) /
                     (float(days) / 250), 2)

    def calc_profitpctchange(self, assets):
        return assets[::-1].pct_change()[::-1]

    def calc_beta(self, assest_profit, benchmark_profit):

        calc_cov = np.cov(assest_profit, benchmark_profit)
        beta = calc_cov[0, 1] / calc_cov[1, 1]
        return beta

    def calc_alpha(self,
                   annualized_returns,
                   benchmark_annualized_returns,
                   beta,
                   r=0.05):

        alpha = (annualized_returns - r) - (beta) *\
            (benchmark_annualized_returns - r)
        return alpha

    def calc_IR(self):
        """计算信息比率

        Returns:
            [type] -- [description]
        """
        if self.volatility == 0:
            return 0
        else:
            return self.annualize_return / self.volatility

    def calc_profit(self, assets):
        """
        计算账户收益
        期末资产/期初资产 -1
        """
        return (float(assets.iloc[-1]) / float(assets.iloc[0])) - 1

    def calc_sharpe(self, annualized_returns, volatility_year, r=0.05):
        """
        计算夏普比率
        r是无风险收益
        """
        # 会出现0
        if volatility_year == 0:
            return 0
        return (annualized_returns - r) / volatility_year

    @property
    def max_holdmarketvalue(self):
        """最大持仓市值

        Returns:
            [type] -- [description]
        """
        if self.daily_market_value is not None:
            return self.daily_market_value.max()
        else:
            return 0

    @property
    def min_holdmarketvalue(self):
        """最小持仓市值

        Returns:
            [type] -- [description]
        """
        if self.daily_market_value is not None:
            return self.daily_market_value.min()
        else:
            return 0

    @property
    def average_holdmarketvalue(self):
        """平均持仓市值

        Returns:
            [type] -- [description]
        """
        if self.daily_market_value is not None:
            return self.daily_market_value.mean()
        else:
            return 0

    @property
    def max_cashhold(self):
        """最大闲置资金
        """

        return self.account.daily_cash.cash.max()

    @property
    def min_cashhold(self):
        """最小闲置资金
        """

        return self.account.daily_cash.cash.min()

    @property
    def average_cashhold(self):
        """平均闲置资金

        Returns:
            [type] -- [description]
        """

        return self.account.daily_cash.cash.mean()

    def save(self):
        """save to mongodb

        """
        save_riskanalysis(self.message)

    def plot_assets_curve(self, length=14, height=12):
        """
        资金曲线叠加图
        @Roy T.Burns 2018/05/29 修改百分比显示错误
        """
        plt.style.use('ggplot')
        plt.figure(figsize=(length, height))
        plt.subplot(211)
        plt.title('BASIC INFO', fontsize=12)
        plt.axis([0, length, 0, 0.6])
        plt.axis('off')
        i = 0
        for item in ['account_cookie', 'portfolio_cookie', 'user_cookie']:
            plt.text(i,
                     0.5,
                     '{} : {}'.format(item, self.message[item]),
                     fontsize=10,
                     rotation=0,
                     wrap=True)
            i += (length / 2.8)
        i = 0
        for item in ['benchmark_code', 'time_gap', 'max_dropback']:
            plt.text(i,
                     0.4,
                     '{} : {}'.format(item, self.message[item]),
                     fontsize=10,
                     ha='left',
                     rotation=0,
                     wrap=True)
            i += (length / 2.8)
        i = 0
        for item in ['annualize_return', 'bm_annualizereturn', 'profit']:
            plt.text(i,
                     0.3,
                     '{} : {} %'.format(item,
                                        self.message.get(item, 0) * 100),
                     fontsize=10,
                     ha='left',
                     rotation=0,
                     wrap=True)
            i += length / 2.8
        i = 0
        for item in ['init_cash', 'last_assets', 'volatility']:
            plt.text(i,
                     0.2,
                     '{} : {} '.format(item, self.message[item]),
                     fontsize=10,
                     ha='left',
                     rotation=0,
                     wrap=True)
            i += length / 2.8
        i = 0
        for item in ['alpha', 'beta', 'sharpe']:
            plt.text(i,
                     0.1,
                     '{} : {}'.format(item, self.message[item]),
                     ha='left',
                     fontsize=10,
                     rotation=0,
                     wrap=True)
            i += length / 2.8
        plt.subplot(212)
        self.assets.plot()
        self.benchmark_assets.xs(self.benchmark_code, level=1).plot()

        asset_p = mpatches.Patch(color='red',
                                 label='{}'.format(
                                     self.account.account_cookie))
        asset_b = mpatches.Patch(
            label='benchmark {}'.format(self.benchmark_code))
        plt.legend(handles=[asset_p, asset_b], loc=1)
        plt.title('ASSET AND BENCKMARK')

        return plt

    @property
    def month_assets(self):
        return self.assets.resample('M').last()

    @property
    def month_assets_profit(self):

        res = pd.concat([pd.Series(self.assets.iloc[0]),
                         self.month_assets]).diff().dropna()
        res.index = res.index.map(str)
        return res

    @property
    def daily_assets_profit(self):
        return self.assets.diff()

    def plot_dailyhold(self, start=None, end=None):
        """
        使用热力图画出每日持仓
        """
        start = self.account.start_date if start is None else start
        end = self.account.end_date if end is None else end
        _, ax = plt.subplots(figsize=(20, 8))
        sns.heatmap(self.account.daily_hold.reset_index().drop(
            'account_cookie', axis=1).set_index('date').loc[start:end],
                    cmap="YlGnBu",
                    linewidths=0.05,
                    ax=ax)
        ax.set_title('HOLD TABLE --ACCOUNT: {}'.format(
            self.account.account_cookie))
        ax.set_xlabel('Code')
        ax.set_ylabel('DATETIME')

        return plt

    def plot_signal(self, start=None, end=None):
        """
        使用热力图画出买卖信号
        """
        start = self.account.start_date if start is None else start
        end = self.account.end_date if end is None else end
        _, ax = plt.subplots(figsize=(20, 18))
        sns.heatmap(self.account.trade.reset_index().drop(
            'account_cookie', axis=1).set_index('datetime').loc[start:end],
                    cmap="YlGnBu",
                    linewidths=0.05,
                    ax=ax)
        ax.set_title('SIGNAL TABLE --ACCOUNT: {}'.format(
            self.account.account_cookie))
        ax.set_xlabel('Code')
        ax.set_ylabel('DATETIME')
        return plt

    def generate_plots(self):
        """
        生成图像
        """
        self.plot_assets_curve()
        self.plot_dailyhold()
        self.plot_signal()
Пример #3
0
    def __init__(
            self,
            account,
            benchmark_code='000300',
            benchmark_type=MARKET_TYPE.INDEX_CN,
            if_fq=True,
            market_data=None,
            auto_reload=False
    ):
        """
        account: QA_Account类/QA_PortfolioView类
        benchmark_code: [str]对照参数代码
        benchmark_type: [QA.PARAM]对照参数的市场
        if_fq: [Bool]原account是否使用复权数据
        if_fq选项是@尧提出的,关于回测的时候成交价格问题(如果按不复权撮合 应该按不复权价格计算assets)
        """
        self.account = account
        self.benchmark_code = benchmark_code  # 默认沪深300
        self.benchmark_type = benchmark_type
        self.client = DATABASE.risk

        self.client.create_index(
            [
                ("account_cookie",
                 ASCENDING),
                ("user_cookie",
                 ASCENDING),
                ("portfolio_cookie",
                 ASCENDING)
            ],
            unique=True
        )
        if auto_reload:
            pass
        else:
            self.fetch = {
                MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv,
                MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv
            }
            if market_data == None:
                if self.account.market_type == MARKET_TYPE.STOCK_CN:
                    self.market_data = QA_fetch_stock_day_adv(
                        self.account.code,
                        self.account.start_date,
                        self.account.end_date
                    )
                elif self.account.market_type == MARKET_TYPE.FUTURE_CN:
                    self.market_data = QA_fetch_future_day_adv(
                        self.account.code,
                        self.account.start_date,
                        self.account.end_date
                    )
            else:
                self.market_data = market_data
            self.if_fq = if_fq
            if self.account.market_type == MARKET_TYPE.FUTURE_CN:
                self.if_fq = False  # 如果是期货, 默认设为FALSE

            if self.market_value is not None:
                if self.account.market_type == MARKET_TYPE.FUTURE_CN and self.account.allow_margin == True:
                    print('margin!')
                    self._assets = (
                        self.account.daily_frozen +
                        self.account.daily_cash.set_index('date').cash
                    ).dropna()
                else:
                    self._assets = (
                        self.market_value.sum(axis=1) +
                        self.account.daily_cash.set_index('date').cash
                    ).fillna(method='pad')
            else:
                self._assets = self.account.daily_cash.set_index('date'
                                                                 ).cash.fillna(
                    method='pad'
                )

            self.time_gap = QA_util_get_trade_gap(
                self.account.start_date,
                self.account.end_date
            )
            self.init_cash = self.account.init_cash
            self.init_assets = self.account.init_assets
Пример #4
0
class QA_Risk():
    """QARISK 是一个风险插件

    需要加载一个account/portfolio类进来:
    需要有
    code,start_date,end_date,daily_cash,daily_hold

    TODO:
    资金利用率 反应资金的利用程度
    股票周转率 反应股票的持仓天数
    预期PNL/统计学PNL
    """

    def __init__(
            self,
            account,
            benchmark_code='000300',
            benchmark_type=MARKET_TYPE.INDEX_CN,
            if_fq=True,
            market_data=None,
            auto_reload=False
    ):
        """
        account: QA_Account类/QA_PortfolioView类
        benchmark_code: [str]对照参数代码
        benchmark_type: [QA.PARAM]对照参数的市场
        if_fq: [Bool]原account是否使用复权数据
        if_fq选项是@尧提出的,关于回测的时候成交价格问题(如果按不复权撮合 应该按不复权价格计算assets)
        """
        self.account = account
        self.benchmark_code = benchmark_code  # 默认沪深300
        self.benchmark_type = benchmark_type
        self.client = DATABASE.risk

        self.client.create_index(
            [
                ("account_cookie",
                 ASCENDING),
                ("user_cookie",
                 ASCENDING),
                ("portfolio_cookie",
                 ASCENDING)
            ],
            unique=True
        )
        if auto_reload:
            pass
        else:
            self.fetch = {
                MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv,
                MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv
            }
            if market_data == None:
                if self.account.market_type == MARKET_TYPE.STOCK_CN:
                    self.market_data = QA_fetch_stock_day_adv(
                        self.account.code,
                        self.account.start_date,
                        self.account.end_date
                    )
                elif self.account.market_type == MARKET_TYPE.FUTURE_CN:
                    self.market_data = QA_fetch_future_day_adv(
                        self.account.code,
                        self.account.start_date,
                        self.account.end_date
                    )
            else:
                self.market_data = market_data
            self.if_fq = if_fq
            if self.account.market_type == MARKET_TYPE.FUTURE_CN:
                self.if_fq = False  # 如果是期货, 默认设为FALSE

            if self.market_value is not None:
                if self.account.market_type == MARKET_TYPE.FUTURE_CN and self.account.allow_margin == True:
                    print('margin!')
                    self._assets = (
                        self.account.daily_frozen +
                        self.account.daily_cash.set_index('date').cash
                    ).dropna()
                else:
                    self._assets = (
                        self.market_value.sum(axis=1) +
                        self.account.daily_cash.set_index('date').cash
                    ).fillna(method='pad')
            else:
                self._assets = self.account.daily_cash.set_index('date'
                                                                 ).cash.fillna(
                    method='pad'
                )

            self.time_gap = QA_util_get_trade_gap(
                self.account.start_date,
                self.account.end_date
            )
            self.init_cash = self.account.init_cash
            self.init_assets = self.account.init_assets

    def __repr__(self):
        return '< QA_RISK ANALYSIS ACCOUNT/PORTFOLIO >'

    def __call__(self):
        return pd.DataFrame([self.message])

    @property
    def total_timeindex(self):
        return self.account.trade_range

    @property
    def market_value(self):
        """每日每个股票持仓市值表

        Returns:
            pd.DataFrame -- 市值表
        """
        if self.account.daily_hold is not None:
            if self.if_fq:

                return (
                    self.market_data.to_qfq().pivot('close').fillna(
                        method='ffill'
                    ) * self.account.daily_hold.apply(abs)
                ).fillna(method='ffill')
            else:
                return (
                    self.market_data.pivot('close').fillna(method='ffill') *
                    self.account.daily_hold.apply(abs)
                ).fillna(method='ffill')
        else:
            return None

    @property
    def daily_market_value(self):
        """每日持仓总市值表

        Returns:
            pd.DataFrame -- 市值表
        """
        if self.market_value is not None:
            return self.market_value.sum(axis=1)
        else:
            return None

    @property
    def assets(self):
        x1 = self._assets.reset_index()
        return x1.assign(date=pd.to_datetime(x1.date)).set_index('date')[0]

    @property
    def max_dropback(self):
        """最大回撤
        """
        return round(
            float(
                max(
                    [
                        (self.assets.iloc[idx] - self.assets.iloc[idx::].min())
                        / self.assets.iloc[idx]
                        for idx in range(len(self.assets))
                    ]
                )
            ),
            2
        )

    @property
    def total_commission(self):
        """总手续费
        """
        return float(
            -abs(round(self.account.history_table.commission.sum(),
                       2))
        )

    @property
    def total_tax(self):
        """总印花税

        """

        return float(-abs(round(self.account.history_table.tax.sum(), 2)))

    @property
    def profit_construct(self):
        """利润构成

        Returns:
            dict -- 利润构成表
        """

        return {
            'total_buyandsell':
            round(
                self.profit_money - self.total_commission - self.total_tax,
                2
            ),
            'total_tax':
            self.total_tax,
            'total_commission':
            self.total_commission,
            'total_profit':
            self.profit_money
        }

    @property
    def profit_money(self):
        """盈利额

        Returns:
            [type] -- [description]
        """

        return float(round(self.assets.iloc[-1] - self.assets.iloc[0], 2))

    @property
    def profit(self):
        """盈利率(百分比)

        Returns:
            [type] -- [description]
        """

        return round(float(self.calc_profit(self.assets)), 2)

    @property
    def profit_pct(self):
        """利润
        """
        return self.calc_profitpctchange(self.assets)

    @property
    def annualize_return(self):
        """年化收益

        Returns:
            [type] -- [description]
        """

        return round(
            float(self.calc_annualize_return(self.assets,
                                             self.time_gap)),
            2
        )

    @property
    def volatility(self):
        """波动率

        Returns:
            [type] -- [description]
        """
        return round(float(self.profit_pct.std() * math.sqrt(250)), 2)

    @property
    def ir(self):
        return round(self.calc_IR(), 2)

    @property
    @lru_cache()
    def message(self):
        return {
            'account_cookie': self.account.account_cookie,
            'portfolio_cookie': self.account.portfolio_cookie,
            'user_cookie': self.account.user_cookie,
            'annualize_return': round(self.annualize_return,
                                      2),
            'profit': round(self.profit,
                            2),
            'max_dropback': self.max_dropback,
            'time_gap': self.time_gap,
            'volatility': self.volatility,
            'benchmark_code': self.benchmark_code,
            'bm_annualizereturn': self.benchmark_annualize_return,
            'bm_profit': self.benchmark_profit,
            'beta': self.beta,
            'alpha': self.alpha,
            'sharpe': self.sharpe,
            'init_cash': "%0.2f" % (float(self.assets[0])),
            'last_assets': "%0.2f" % (float(self.assets.iloc[-1])),
            'total_tax': self.total_tax,
            'total_commission': self.total_commission,
            'profit_money': self.profit_money,
            'assets': list(self.assets),
            'benchmark_assets': list(self.benchmark_assets),
            'timeindex': self.account.trade_day,
            'totaltimeindex': self.total_timeindex,
            'ir': self.ir,
            'month_profit': self.month_assets_profit.to_dict()
            # 'init_assets': round(float(self.init_assets), 2),
            # 'last_assets': round(float(self.assets.iloc[-1]), 2)
        }

    @property
    def benchmark_data(self):
        """
        基准组合的行情数据(一般是组合,可以调整)
        """
        return self.fetch[self.benchmark_type](
            self.benchmark_code,
            self.account.start_date,
            self.account.end_date
        )

    @property
    def benchmark_assets(self):
        """
        基准组合的账户资产队列
        """
        return (
            self.benchmark_data.close /
            float(self.benchmark_data.close.iloc[0])
            * float(self.assets[0])
        )

    @property
    def benchmark_profit(self):
        """
        基准组合的收益
        """
        return round(float(self.calc_profit(self.benchmark_assets)), 2)

    @property
    def benchmark_annualize_return(self):
        """基准组合的年化收益

        Returns:
            [type] -- [description]
        """

        return round(
            float(
                self.calc_annualize_return(
                    self.benchmark_assets,
                    self.time_gap
                )
            ),
            2
        )

    @property
    def benchmark_profitpct(self):
        """
        benchmark 基准组合的收益百分比计算
        """
        return self.calc_profitpctchange(self.benchmark_assets)

    @property
    def beta(self):
        """
        beta比率 组合的系统性风险
        """
        try:
            res = round(
                float(
                    self.calc_beta(
                        self.profit_pct.dropna(),
                        self.benchmark_profitpct.dropna()
                    )
                ),
                2
            )
        except:
            print('贝塔计算错误。。')
            res = 0

        return res

    @property
    def alpha(self):
        """
        alpha比率 与市场基准收益无关的超额收益率
        """
        return round(
            float(
                self.calc_alpha(
                    self.annualize_return,
                    self.benchmark_annualize_return,
                    self.beta,
                    0.05
                )
            ),
            2
        )

    @property
    def sharpe(self):
        """
        夏普比率

        """
        return round(
            float(
                self.calc_sharpe(self.annualize_return,
                                 self.volatility,
                                 0.05)
            ),
            2
        )

    @property
    def sortino(self):
        """
        索提诺比率 投资组合收益和下行风险比值

        """
        pass

    @property
    def calmar(self):
        """
        卡玛比率
        """
        pass

    def set_benchmark(self, code, market_type):
        self.benchmark_code = code
        self.benchmark_type = market_type

    def calc_annualize_return(self, assets, days):
        return round(
            (float(assets.iloc[-1]) / float(assets.iloc[0]) - 1) /
            (float(days) / 250),
            2
        )

    def calc_profitpctchange(self, assets):
        return assets[::-1].pct_change()[::-1]

    def calc_beta(self, assest_profit, benchmark_profit):

        calc_cov = np.cov(assest_profit, benchmark_profit)
        beta = calc_cov[0, 1] / calc_cov[1, 1]
        return beta

    def calc_alpha(
            self,
            annualized_returns,
            benchmark_annualized_returns,
            beta,
            r=0.05
    ):

        alpha = (annualized_returns - r) - (beta) *\
            (benchmark_annualized_returns - r)
        return alpha

    def calc_IR(self):
        """计算信息比率

        Returns:
            [type] -- [description]
        """
        if self.volatility == 0:
            return 0
        else:
            return self.annualize_return / self.volatility

    def calc_profit(self, assets):
        """
        计算账户收益
        期末资产/期初资产 -1
        """
        return (float(assets.iloc[-1]) / float(assets.iloc[0])) - 1

    def calc_sharpe(self, annualized_returns, volatility_year, r=0.05):
        """
        计算夏普比率
        r是无风险收益
        """
        # 会出现0
        if volatility_year == 0:
            return 0
        return (annualized_returns - r) / volatility_year

    @property
    def max_holdmarketvalue(self):
        """最大持仓市值

        Returns:
            [type] -- [description]
        """
        if self.daily_market_value is not None:
            return self.daily_market_value.max()
        else:
            return 0

    @property
    def min_holdmarketvalue(self):
        """最小持仓市值

        Returns:
            [type] -- [description]
        """
        if self.daily_market_value is not None:
            return self.daily_market_value.min()
        else:
            return 0

    @property
    def average_holdmarketvalue(self):
        """平均持仓市值

        Returns:
            [type] -- [description]
        """
        if self.daily_market_value is not None:
            return self.daily_market_value.mean()
        else:
            return 0

    @property
    def max_cashhold(self):
        """最大闲置资金
        """

        return self.account.daily_cash.cash.max()

    @property
    def min_cashhold(self):
        """最小闲置资金
        """

        return self.account.daily_cash.cash.min()

    @property
    def average_cashhold(self):
        """平均闲置资金

        Returns:
            [type] -- [description]
        """

        return self.account.daily_cash.cash.mean()

    def save(self):
        """save to mongodb

        """
        save_riskanalysis(self.message)

    def plot_assets_curve(self, length=14, height=12):
        """
        资金曲线叠加图
        @Roy T.Burns 2018/05/29 修改百分比显示错误
        """
        plt.style.use('ggplot')
        plt.figure(figsize=(length, height))
        plt.subplot(211)
        plt.title('BASIC INFO', fontsize=12)
        plt.axis([0, length, 0, 0.6])
        plt.axis('off')
        i = 0
        for item in ['account_cookie', 'portfolio_cookie', 'user_cookie']:
            plt.text(
                i,
                0.5,
                '{} : {}'.format(item,
                                 self.message[item]),
                fontsize=10,
                rotation=0,
                wrap=True
            )
            i += (length / 2.8)
        i = 0
        for item in ['benchmark_code', 'time_gap', 'max_dropback']:
            plt.text(
                i,
                0.4,
                '{} : {}'.format(item,
                                 self.message[item]),
                fontsize=10,
                ha='left',
                rotation=0,
                wrap=True
            )
            i += (length / 2.8)
        i = 0
        for item in ['annualize_return', 'bm_annualizereturn', 'profit']:
            plt.text(
                i,
                0.3,
                '{} : {} %'.format(item,
                                   self.message.get(item,
                                                    0) * 100),
                fontsize=10,
                ha='left',
                rotation=0,
                wrap=True
            )
            i += length / 2.8
        i = 0
        for item in ['init_cash', 'last_assets', 'volatility']:
            plt.text(
                i,
                0.2,
                '{} : {} '.format(item,
                                  self.message[item]),
                fontsize=10,
                ha='left',
                rotation=0,
                wrap=True
            )
            i += length / 2.8
        i = 0
        for item in ['alpha', 'beta', 'sharpe']:
            plt.text(
                i,
                0.1,
                '{} : {}'.format(item,
                                 self.message[item]),
                ha='left',
                fontsize=10,
                rotation=0,
                wrap=True
            )
            i += length / 2.8
        plt.subplot(212)
        self.assets.plot()
        self.benchmark_assets.xs(self.benchmark_code, level=1).plot()

        asset_p = mpatches.Patch(
            color='red',
            label='{}'.format(self.account.account_cookie)
        )
        asset_b = mpatches.Patch(
            label='benchmark {}'.format(self.benchmark_code)
        )
        plt.legend(handles=[asset_p, asset_b], loc=0)
        plt.title('ASSET AND BENCKMARK')

        return plt

    @property
    def month_assets(self):
        return self.assets.resample('M').last()

    @property
    def month_assets_profit(self):

        res = pd.concat([pd.Series(self.assets.iloc[0]),
                         self.month_assets]).diff().dropna()
        res.index = res.index.map(str)
        return res

    @property
    def daily_assets_profit(self):
        return self.assets.diff()

    def plot_dailyhold(self, start=None, end=None):
        """
        使用热力图画出每日持仓
        """
        start = self.account.start_date if start is None else start
        end = self.account.end_date if end is None else end
        _, ax = plt.subplots(figsize=(20, 8))
        sns.heatmap(
            self.account.daily_hold.reset_index().drop(
                'account_cookie',
                axis=1
            ).set_index('date').loc[start:end],
            cmap="YlGnBu",
            linewidths=0.05,
            ax=ax
        )
        ax.set_title(
            'HOLD TABLE --ACCOUNT: {}'.format(self.account.account_cookie)
        )
        ax.set_xlabel('Code')
        ax.set_ylabel('DATETIME')

        return plt

    def plot_signal(self, start=None, end=None):
        """
        使用热力图画出买卖信号
        """
        start = self.account.start_date if start is None else start
        end = self.account.end_date if end is None else end
        _, ax = plt.subplots(figsize=(20, 18))
        sns.heatmap(
            self.account.trade.reset_index().drop(
                'account_cookie',
                axis=1
            ).set_index('datetime').loc[start:end],
            cmap="YlGnBu",
            linewidths=0.05,
            ax=ax
        )
        ax.set_title(
            'SIGNAL TABLE --ACCOUNT: {}'.format(self.account.account_cookie)
        )
        ax.set_xlabel('Code')
        ax.set_ylabel('DATETIME')
        return plt

    def generate_plots(self):
        """
        生成图像
        """
        self.plot_assets_curve()
        self.plot_dailyhold()
        self.plot_signal()