示例#1
0
class QA_Performance():
    """
    QA_Performance是一个绩效分析插件

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


    QAPERFORMANCE 的评估字段

    1. 对于多头开仓/ 空头开仓的分析
    2. 总盈利(对于每个单笔而言)
    3. 总亏损(对于每个单笔而言)
    4. 总盈利/总亏损
    5. 交易手数
    6. 盈利比例
    7. 盈利手数
    8. 亏损手数
    9. 持平手数
    10. 平均利润
    11. 平均盈利
    12. 平均亏损
    13. 平均盈利/平均亏损
    14. 最大盈利(单笔)
    15. 最大亏损(单笔)
    16. 最大盈利/总盈利
    17. 最大亏损/总亏损
    18. 净利润/最大亏损
    19. 最大连续盈利手数
    20. 最大连续亏损手数
    21. 平均持仓周期
    22. 平均盈利周期
    23. 平均亏损周期
    24. 平均持平周期
    25. 最大使用资金
    26. 最大持仓手数
    27. 交易成本合计
    28. 收益率
    29. 年化收益率
    30. 有效收益率
    31. 月度平均盈利
    32. 收益曲线斜率
    33. 收益曲线截距
    34. 收益曲线R2值
    35. 夏普比例
    36. 总交易时间
    37. 总持仓时间
    38. 持仓时间比例
    39. 最大空仓时间
    40. 持仓周期
    41. 资产最大升水
    42. 发生时间
    43. 最大升水/前期低点
    44. 单日最大资产回撤比率
    45. 最大资产回撤值
    46. 最大资产回撤发生时间
    47. 回撤值/前期高点
    48. 净利润/回撤值


    """

    def __init__(self, target):

        self.target = target
        self._style_title = [
            'beta',
            'momentum',
            'size',
            'earning_yield',
            'volatility',
            'growth',
            'value',
            'leverage',
            'liquidity',
            'reversal'
        ]
        self.market_preset = MARKET_PRESET()
        self.pnl = self.pnl_fifo

    def __repr__(self):
        return '< QA_PERFORMANCE ANYLYSIS PLUGIN >'

    def set_pnl(self, model='fifo'):
        if model == 'fifo':
            self.pnl = self.pnl_fifo
        elif model == 'lifo':
            self.pnl = self.pnl_lifo

    def base_message(self, pnl):
        return {'total_profit': round(self.total_profit(pnl), 2),  # 总盈利(对于每个单笔而言)
                'total_loss': round(self.total_loss(pnl), 2),  # 总亏损(对于每个单笔而言)
                'total_pnl': round(self.total_pnl(pnl), 2),  # 总盈利/总亏损
                'trading_amounts': round(self.trading_amounts(pnl), 2),  # 交易手数
                'profit_amounts': round(self.profit_amounts(pnl), 2),  # 盈利手数
                'loss_amounts': round(self.loss_amounts(pnl), 2),  # 亏损手数
                'even_amounts': round(self.even_amounts(pnl), 2),  # 持平手数
                'profit_precentage': round(self.profit_precentage(pnl), 2),
                'loss_precentage': round(self.loss_precentage(pnl), 2),
                'even_precentage': round(self.even_precentage(pnl), 2),
                'average_profit': round(self.average_profit(pnl), 2),
                'average_loss': round(self.average_loss(pnl), 2),
                'average_pnl': round(self.average_pnl(pnl), 2),
                'max_profit': round(self.max_profit(pnl), 2),
                'max_loss': round(self.max_loss(pnl), 2),
                'max_pnl': round(self.max_pnl(pnl), 2),
                'netprofio_maxloss_ratio': round(self.netprofio_maxloss_ratio(pnl), 2),
                'continue_profit_amount': round(self.continue_profit_amount(pnl), 2),
                'continue_loss_amount': round(self.continue_loss_amount(pnl), 2),
                'average_holdgap': self.average_holdgap(pnl),
                'average_profitholdgap': self.average_profitholdgap(pnl),
                'average_losssholdgap': self.average_losssholdgap(pnl)}

    @property
    def message(self):
        """[summary]
            2. 
            3. 
            4. 
            5. 
            6. 
            7. 盈利手数
            8. 亏损手数
            9. 持平手数
            10. 平均利润
            11. 平均盈利
            12. 平均亏损
            13. 平均盈利/平均亏损
            14. 最大盈利(单笔)
            15. 最大亏损(单笔)
            16. 最大盈利/总盈利
            17. 最大亏损/总亏损
            18. 净利润/最大亏损
            19. 最大连续盈利手数
            20. 最大连续亏损手数
            21. 平均持仓周期
            22. 平均盈利周期
            23. 平均亏损周期
            24. 平均持平周期
            25. 最大使用资金
            26. 最大持仓手数
            27. 交易成本合计
            28. 收益率
            29. 年化收益率
            30. 有效收益率
            31. 月度平均盈利
            32. 收益曲线斜率
            33. 收益曲线截距
            34. 收益曲线R2值
            35. 夏普比例
            36. 总交易时间
            37. 总持仓时间
            38. 持仓时间比例
            39. 最大空仓时间
            40. 持仓周期
            41. 资产最大升水
            42. 发生时间
            43. 最大升水/前期低点
            44. 单日最大资产回撤比率
            45. 最大资产回撤值
            46. 最大资产回撤发生时间
            47. 回撤值/前期高点
            48. 净利润/回撤值
        Returns:
            [type] -- [description]
        """

        return {
            # 总盈利(对于每个单笔而言)
            'total_profit': round(self.total_profit(self.pnl), 2),
            'total_loss': round(self.total_loss(self.pnl), 2),  # 总亏损(对于每个单笔而言)
            'total_pnl': round(self.total_pnl(self.pnl), 2),  # 总盈利/总亏损
            # 交易手数
            'trading_amounts': round(self.trading_amounts(self.pnl), 2),
            'profit_amounts': round(self.profit_amounts(self.pnl), 2),  # 盈利手数
            'loss_amounts': round(self.loss_amounts(self.pnl), 2),  # 亏损手数
            'even_amounts': round(self.even_amounts(self.pnl), 2),  # 持平手数
            'profit_precentage': round(self.profit_precentage(self.pnl), 2),
            'loss_precentage': round(self.loss_precentage(self.pnl), 2),
            'even_precentage': round(self.even_precentage(self.pnl), 2),
            'average_profit': round(self.average_profit(self.pnl), 2),
            'average_loss': round(self.average_loss(self.pnl), 2),
            'average_pnl': round(self.average_pnl(self.pnl), 2),
            'max_profit': round(self.max_profit(self.pnl), 2),
            'max_loss': round(self.max_loss(self.pnl), 2),
            'max_pnl': round(self.max_pnl(self.pnl), 2),
            'netprofio_maxloss_ratio': round(self.netprofio_maxloss_ratio(self.pnl), 2),
            'continue_profit_amount': round(self.continue_profit_amount(self.pnl), 2),
            'continue_loss_amount': round(self.continue_loss_amount(self.pnl), 2),
            'average_holdgap': self.average_holdgap(self.pnl),
            'average_profitholdgap': self.average_profitholdgap(self.pnl),
            'average_losssholdgap': self.average_losssholdgap(self.pnl),
            'buyopen': self.base_message(self.pnl_buyopen),
            'sellopen': self.base_message(self.pnl_sellopen)
        }

    @property
    def prefer(self):
        pass

    @property
    def style(self):
        """风格分析
        """
        pass

    @property
    def pnl_lifo(self):
        """
        使用后进先出法配对成交记录
        """
        X = dict(
            zip(
                self.target.code,
                [{'buy': LifoQueue(), 'sell': LifoQueue()}
                 for i in range(len(self.target.code))]
            )
        )
        pair_table = []
        for _, data in self.target.history_table_min.iterrows():
            while True:

                if data.direction in[1, 2, -2]:
                    if data.direction in [1, 2]:
                        X[data.code]['buy'].append(
                            (data.datetime,
                             data.amount,
                             data.price,
                             data.direction)
                        )
                    elif data.direction in [-2]:
                        X[data.code]['sell'].append(
                            (data.datetime,
                             data.amount,
                             data.price,
                             data.direction)
                        )
                    break
                elif data.direction in[-1, 3, -3]:

                    rawoffset = 'buy' if data.direction in [-1, -3] else 'sell'

                    l = X[data.code][rawoffset].get()
                    if abs(l[1]) > abs(data.amount):
                        """
                        if raw> new_close:
                        """
                        temp = (l[0], l[1] + data.amount, l[2])
                        X[data.code][rawoffset].put_nowait(temp)
                        if data.amount < 0:
                            pair_table.append(
                                [
                                    data.code,
                                    data.datetime,
                                    l[0],
                                    abs(data.amount),
                                    data.price,
                                    l[2],
                                    rawoffset
                                ]
                            )
                            break
                        else:
                            pair_table.append(
                                [
                                    data.code,
                                    l[0],
                                    data.datetime,
                                    abs(data.amount),
                                    l[2],
                                    data.price,
                                    rawoffset
                                ]
                            )
                            break

                    elif abs(l[1]) < abs(data.amount):
                        data.amount = data.amount + l[1]

                        if data.amount < 0:
                            pair_table.append(
                                [
                                    data.code,
                                    data.datetime,
                                    l[0],
                                    l[1],
                                    data.price,
                                    l[2],
                                    rawoffset
                                ]
                            )
                        else:
                            pair_table.append(
                                [
                                    data.code,
                                    l[0],
                                    data.datetime,
                                    l[1],
                                    l[2],
                                    data.price,
                                    rawoffset
                                ]
                            )
                    else:
                        if data.amount < 0:
                            pair_table.append(
                                [
                                    data.code,
                                    data.datetime,
                                    l[0],
                                    abs(data.amount),
                                    data.price,
                                    l[2],
                                    rawoffset
                                ]
                            )
                            break
                        else:
                            pair_table.append(
                                [
                                    data.code,
                                    l[0],
                                    data.datetime,
                                    abs(data.amount),
                                    l[2],
                                    data.price,
                                    rawoffset
                                ]
                            )
                            break

        pair_title = [
            'code',
            'sell_date',
            'buy_date',
            'amount',
            'sell_price',
            'buy_price',
            'rawdirection'
        ]
        pnl = pd.DataFrame(pair_table, columns=pair_title)

        pnl = pnl.assign(
            unit=pnl.code.apply(lambda x: self.market_preset.get_unit(x)),
            pnl_ratio=(pnl.sell_price / pnl.buy_price) - 1,
            sell_date=pd.to_datetime(pnl.sell_date),
            buy_date=pd.to_datetime(pnl.buy_date)
        )
        pnl = pnl.assign(
            pnl_money=(pnl.sell_price - pnl.buy_price) * pnl.amount * pnl.unit,
            hold_gap=abs(pnl.sell_date - pnl.buy_date),
            if_buyopen=pnl.rawdirection == 'buy'
        )
        pnl = pnl.assign(
            openprice=pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) *
            pnl.buy_price +
            pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_price,
            opendate=pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) *
            pnl.buy_date.map(str) +
            pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) *
            pnl.sell_date.map(str),
            closeprice=pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) *
            pnl.buy_price +
            pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_price,
            closedate=pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) *
            pnl.buy_date.map(str) +
            pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) *
            pnl.sell_date.map(str)
        )
        return pnl.set_index('code')

    @property
    def pnl_buyopen(self):
        return self.pnl[self.pnl.if_buyopen]

    @property
    def pnl_sellopen(self):
        return self.pnl[~self.pnl.if_buyopen]

    @property
    def pnl_fifo(self):
        X = dict(
            zip(
                self.target.code,
                [{'buy': deque(), 'sell': deque()}
                 for i in range(len(self.target.code))]
            )
        )
        pair_table = []
        for _, data in self.target.history_table_min.iterrows():
            while True:

                if data.direction in[1, 2, -2]:
                    if data.direction in [1, 2]:
                        X[data.code]['buy'].append(
                            (data.datetime,
                             data.amount,
                             data.price,
                             data.direction)
                        )
                    elif data.direction in [-2]:
                        X[data.code]['sell'].append(
                            (data.datetime,
                             data.amount,
                             data.price,
                             data.direction)
                        )
                    break
                elif data.direction in[-1, 3, -3]:

                    rawoffset = 'buy' if data.direction in [-1, -3] else 'sell'

                    l = X[data.code][rawoffset].popleft()
                    if abs(l[1]) > abs(data.amount):
                        """
                        if raw> new_close:
                        """
                        temp = (l[0], l[1] + data.amount, l[2])
                        X[data.code][rawoffset].appendleft(temp)
                        if data.amount < 0:
                            pair_table.append(
                                [
                                    data.code,
                                    data.datetime,
                                    l[0],
                                    abs(data.amount),
                                    data.price,
                                    l[2],
                                    rawoffset
                                ]
                            )
                            break
                        else:
                            pair_table.append(
                                [
                                    data.code,
                                    l[0],
                                    data.datetime,
                                    abs(data.amount),
                                    l[2],
                                    data.price,
                                    rawoffset
                                ]
                            )
                            break

                    elif abs(l[1]) < abs(data.amount):
                        data.amount = data.amount + l[1]

                        if data.amount < 0:
                            pair_table.append(
                                [
                                    data.code,
                                    data.datetime,
                                    l[0],
                                    l[1],
                                    data.price,
                                    l[2],
                                    rawoffset
                                ]
                            )
                        else:
                            pair_table.append(
                                [
                                    data.code,
                                    l[0],
                                    data.datetime,
                                    l[1],
                                    l[2],
                                    data.price,
                                    rawoffset
                                ]
                            )
                    else:
                        if data.amount < 0:
                            pair_table.append(
                                [
                                    data.code,
                                    data.datetime,
                                    l[0],
                                    abs(data.amount),
                                    data.price,
                                    l[2],
                                    rawoffset
                                ]
                            )
                            break
                        else:
                            pair_table.append(
                                [
                                    data.code,
                                    l[0],
                                    data.datetime,
                                    abs(data.amount),
                                    l[2],
                                    data.price,
                                    rawoffset
                                ]
                            )
                            break

        pair_title = [
            'code',
            'sell_date',
            'buy_date',
            'amount',
            'sell_price',
            'buy_price',
            'rawdirection'
        ]
        pnl = pd.DataFrame(pair_table, columns=pair_title)

        pnl = pnl.assign(
            unit=pnl.code.apply(lambda x: self.market_preset.get_unit(x)),
            pnl_ratio=(pnl.sell_price / pnl.buy_price) - 1,
            sell_date=pd.to_datetime(pnl.sell_date),
            buy_date=pd.to_datetime(pnl.buy_date)
        )
        pnl = pnl.assign(
            pnl_money=(pnl.sell_price - pnl.buy_price) * pnl.amount * pnl.unit,
            hold_gap=abs(pnl.sell_date - pnl.buy_date),
            if_buyopen=pnl.rawdirection == 'buy'
        )
        pnl = pnl.assign(
            openprice=pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) *
            pnl.buy_price +
            pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_price,
            opendate=pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) *
            pnl.buy_date.map(str) +
            pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) *
            pnl.sell_date.map(str),
            closeprice=pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) *
            pnl.buy_price +
            pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_price,
            closedate=pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) *
            pnl.buy_date.map(str) +
            pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) *
            pnl.sell_date.map(str)
        )
        return pnl.set_index('code')

    def plot_pnlratio(self):
        """
        画出pnl比率散点图
        """

        plt.scatter(x=self.pnl.sell_date.apply(str), y=self.pnl.pnl_ratio)
        plt.gcf().autofmt_xdate()
        return plt

    def plot_pnlmoney(self):
        """
        画出pnl盈亏额散点图
        """
        plt.scatter(x=self.pnl.sell_date.apply(str), y=self.pnl.pnl_money)
        plt.gcf().autofmt_xdate()
        return plt

    def abnormal_active(self):
        """
        账户的成交发生异常成交记录的分析
        """
        pass

    def brinson(self):
        """Brinson Model analysis
        """
        pass

    def hold(self):
        """持仓分析
        """
        pass

    def win_rate(self):
        """胜率

        胜率
        盈利次数/总次数
        """
        data = self.pnl
        try:
            return round(len(data.query('pnl_money>0')) / len(data), 2)
        except ZeroDivisionError:
            return 0

    @property
    def accumulate_return(self):
        """
        returns a pd-Dataframe format accumulate return for different periods
        """
        pass

    def save(self):
        """save the performance analysis result to database
        """
        pass

    def profit_pnl(self, pnl):
        return pnl.query('pnl_money>0')

    def loss_pnl(self, pnl):
        return pnl.query('pnl_money<0')

    def even_pnl(self, pnl):
        return pnl.query('pnl_money==0')

    def total_profit(self, pnl):
        if len(self.profit_pnl(pnl)) > 0:
            return self.profit_pnl(pnl).pnl_money.sum()
        else:
            return 0

    def total_loss(self, pnl):
        if len(self.loss_pnl(pnl)) > 0:
            return self.loss_pnl(pnl).pnl_money.sum()
        else:
            return 0

    def total_pnl(self, pnl):
        try:
            return abs(self.total_profit(pnl) / self.total_loss(pnl))
        except ZeroDivisionError:
            return 0

    def trading_amounts(self, pnl):
        return len(pnl)

    def profit_amounts(self, pnl):
        return len(self.profit_pnl(pnl))

    def loss_amounts(self, pnl):
        return len(self.loss_pnl(pnl))

    def even_amounts(self, pnl):
        return len(self.even_pnl(pnl))

    def profit_precentage(self, pnl):
        try:
            return self.profit_amounts(pnl) / self.trading_amounts(pnl)
        except ZeroDivisionError:
            return 0

    def loss_precentage(self, pnl):
        try:
            return self.loss_amounts(pnl) / self.trading_amounts(pnl)
        except ZeroDivisionError:
            return 0

    def even_precentage(self, pnl):
        try:
            return self.even_amounts(pnl) / self.trading_amounts(pnl)
        except ZeroDivisionError:
            return 0

    def average_loss(self, pnl):
        if len(self.loss_pnl(pnl)) > 0:
            return self.loss_pnl(pnl).pnl_money.mean()
        else:
            return 0

    def average_profit(self, pnl):
        if len(self.profit_pnl(pnl)) > 0:
            return self.profit_pnl(pnl).pnl_money.mean()
        else:
            return 0

    def average_pnl(self, pnl):
        if len(self.loss_pnl(pnl)) > 0 and len(self.profit_pnl(pnl)) > 0:
            try:
                return abs(self.average_profit(pnl) / self.average_loss(pnl))
            except ZeroDivisionError:
                return 0
        else:
            return 0

    def max_profit(self, pnl):
        if len(self.profit_pnl(pnl)) > 0:
            return self.profit_pnl(pnl).pnl_money.max()
        else:
            return 0

    def max_loss(self, pnl):
        if len(self.loss_pnl(pnl)) > 0:
            return self.loss_pnl(pnl).pnl_money.min()
        else:
            return 0

    def max_pnl(self, pnl):
        try:
            return abs(self.max_profit(pnl) / self.max_loss(pnl))
        except ZeroDivisionError:
            return 0

    def netprofio_maxloss_ratio(self, pnl):
        if len(self.loss_pnl(pnl)) > 0:
            try:
                return abs(pnl.pnl_money.sum() / self.max_loss(pnl))
            except ZeroDivisionError:
                return 0
        else:
            return 0

    def continue_profit_amount(self, pnl):
        w = []
        w1 = 0
        for _, item in pnl.pnl_money.iteritems():
            if item > 0:
                w1 += 1
            elif item < 0:
                w.append(w1)
                w1 = 0
        if len(w) == 0:
            return 0
        else:
            return max(w)

    def continue_loss_amount(self, pnl):
        l = []
        l1 = 0
        for _, item in pnl.pnl_money.iteritems():
            if item > 0:
                l1 += 1
            elif item < 0:
                l.append(l1)
                l1 = 0
        if len(l) == 0:
            return 0
        else:
            return max(l)

    def average_holdgap(self, pnl):
        if len(pnl.hold_gap) > 0:
            return str(pnl.hold_gap.mean())
        else:
            return 'no trade'

    def average_profitholdgap(self, pnl):
        if len(self.profit_pnl(pnl).hold_gap) > 0:
            return str(self.profit_pnl(pnl).hold_gap.mean())
        else:
            return 'no trade'

    def average_losssholdgap(self, pnl):
        if len(self.loss_pnl(pnl).hold_gap) > 0:
            return str(self.loss_pnl(pnl).hold_gap.mean())
        else:
            return 'no trade'

    def average_evenholdgap(self, pnl):
        if len(self.even_pnl(pnl).hold_gap) > 0:
            return self.even_pnl(pnl).hold_gap.mean()
        else:
            return 'no trade'

    @property
    def max_cashused(self):
        return self.target.init_cash - min(self.target.cash)

    @property
    def total_taxfee(self):
        return self.target.history_table_min.commission.sum(
        ) + self.target.history_table_min.tax.sum()
示例#2
0
class QA_Position():
    """一个持仓模型:/兼容股票期货/兼容保证金持仓

    兼容快期DIFF协议

    基础字段

    code [str]  品种名称 
    volume_long_today [float] 今仓 多单持仓
    volume_long_his   [float] 昨仓 多单持仓
    volume_short_today [float] 今仓 空单持仓
    volume_short_his  [float] 昨仓 空单持仓

    volume_long_frozen_his [float] 多单昨日冻结
    volume_long_frozen_today [float] 多单今日冻结
    volume_short_frozen_his [float] 空单昨日冻结
    volume_short_frozen_today [float] 空单今日冻结

    margin_long  [float] 多单保证金
    margin_short [float] 空单保证金

    open_price_long [float] 多单开仓价
    open_price_short [float] 空单开仓价
    position_price_long [float] 逐日盯市的前一交易日的结算价
    position_price_short [float] 逐日盯市的前一交易日的结算价

    open_cost_long [float] 多单开仓成本(指的是保证金冻结)
    open_cost_short [float] 空单开仓成本
    position_cost_long [float] 多单持仓成本(指的是基于逐日盯市制度下的成本价)
    position_cost_short [float] 空单持仓成本


    market_type=MARKET_TYPE.STOCK_CN [enum] 市场类别
    exchange_id=EXCHANGE_ID.SZSE [enum] 交易所id(支持股票/期货)
    name=None [str] 名称


    功能:

    1/ 支持当价格变化后的 持仓的自行计算更新
    2/ 支持调仓模型(未加入)
    3/ 支持仓位风控(未加入)
    4/ 支持资金分配和PMS内部资金结算(moneypreset)

    PMS 内部可以预分配一个资金限额, 方便pms实时计算属于PMS的收益

    兼容QA_Account的创建/拆入Positions库

    QAPosition 不对订单信息做正确性保证, 需要自行在外部构建 OMS系统 {QACEPEngine/QAAccountPro}

    """
    def __init__(
            self,
            code='000001',
            account_cookie='quantaxis',
            portfolio_cookie='portfolio',
            username='******',
            moneypreset=100000,  # 初始分配资金
            frozen=None,
            moneypresetLeft=None,
            volume_long_today=0,
            volume_long_his=0,
            volume_short_today=0,
            volume_short_his=0,
            volume_long_frozen_his=0,
            volume_long_frozen_today=0,
            volume_short_frozen_his=0,
            volume_short_frozen_today=0,
            margin_long=0,
            margin_short=0,
            open_price_long=0,
            open_price_short=0,
            position_price_long=0,  # 逐日盯市的前一交易日的结算价
            position_price_short=0,  # 逐日盯市的前一交易日的结算价
            open_cost_long=0,
            open_cost_short=0,
            position_cost_long=0,
            position_cost_short=0,
            position_id=None,
            market_type=None,
            exchange_id=None,
            trades=None,
            orders=None,
            name=None,
            commission=0,
            auto_reload=False,
            allow_exceed=False,
            spms_id=None,
            oms_id=None,
            *args,
            **kwargs):

        self.code = code
        self.account_cookie = account_cookie
        self.portfolio_cookie = portfolio_cookie
        self.username = username
        self.time = ''
        self.market_preset = MARKET_PRESET().get_code(self.code)
        self.position_id = str(
            uuid.uuid4()) if position_id is None else position_id
        self.moneypreset = moneypreset
        self.moneypresetLeft = self.moneypreset if moneypresetLeft is None else moneypresetLeft
        """{'name': '原油',
            'unit_table': 1000,
            'price_tick': 0.1,
            'buy_frozen_coeff': 0.1,
            'sell_frozen_coeff': 0.1,
            'exchange': 'INE',
            'commission_coeff_peramount': 0,
            'commission_coeff_pervol': 20.0,
            'commission_coeff_today_peramount': 0,
            'commission_coeff_today_pervol': 0.0}
        """
        self.rule = 'FIFO'
        self.name = name

        if market_type is None:

            self.market_type = MARKET_TYPE.FUTURE_CN if re.search(
                r'[a-zA-z]+', self.code) else MARKET_TYPE.STOCK_CN
        self.exchange_id = exchange_id

        self.volume_long_his = volume_long_his
        self.volume_long_today = volume_long_today
        self.volume_short_his = volume_short_his
        self.volume_short_today = volume_short_today

        self.volume_long_frozen_his = volume_long_frozen_his
        self.volume_long_frozen_today = volume_long_frozen_today
        self.volume_short_frozen_his = volume_short_frozen_his
        self.volume_short_frozen_today = volume_short_frozen_today

        self.margin_long = margin_long
        self.margin_short = margin_short

        self.open_price_long = open_price_long
        self.open_price_short = open_price_short

        self.position_price_long = open_price_long if position_price_long == 0 else position_price_long
        self.position_price_short = open_price_short if position_price_short == 0 else position_price_short

        self.open_cost_long = open_cost_long if open_cost_long != 0 else open_price_long * \
            self.volume_long*self.market_preset.get('unit_table', 1)
        self.open_cost_short = open_cost_short if open_cost_short != 0 else open_price_short * \
            self.volume_short*self.market_preset.get('unit_table', 1)
        self.position_cost_long = position_cost_long if position_cost_long != 0 else self.position_price_long * \
            self.volume_long*self.market_preset.get('unit_table', 1)
        self.position_cost_short = position_cost_short if position_cost_short != 0 else self.position_price_short * \
            self.volume_short*self.market_preset.get('unit_table', 1)

        self.last_price = 0
        self.commission = commission
        self.trades = [] if trades is None else trades
        self.orders = {} if orders is None else orders
        self.frozen = {} if frozen is None else frozen
        self.spms_id = spms_id
        self.oms_id = oms_id
        if auto_reload:
            self.reload()
        self.allow_exceed = allow_exceed

    def __repr__(self):
        return '< QAPOSITION {} amount {}/{} >'.format(self.code,
                                                       self.volume_long,
                                                       self.volume_short)

    def read_diff(self, diff_slice):
        """[summary]

        Arguments:
            diff_slice {dict} -- [description]

            {'user_id': '100002',
            'exchange_id': 'SHFE',
            'instrument_id': 'rb1905',
            'volume_long_today': 0,
            'volume_long_his': 0,
            'volume_long': 0,
            'volume_long_frozen_today': 0,
            'volume_long_frozen_his': 0,
            'volume_long_frozen': 0,
            'volume_short_today': 0,
            'volume_short_his': 0,
            'volume_short': 0,
            'volume_short_frozen_today': 0,
            'volume_short_frozen_his': 0,
            'volume_short_frozen': 0,
            'open_price_long': 4193.0,
            'open_price_short': 4192.0,
            'open_cost_long': 0.0,
            'open_cost_short': 0.0,
            'position_price_long': 4193.0,
            'position_price_short': 4192.0,
            'position_cost_long': 0.0,
            'position_cost_short': 0.0,
            'last_price': 4137.0,
            'float_profit_long': 0.0,
            'float_profit_short': 0.0,
            'float_profit': 0.0,
            'position_profit_long': 0.0,
            'position_profit_short': 0.0,
            'position_profit': 0.0,
            'margin_long': 0.0,
            'margin_short': 0.0,
            'margin': 0.0}

        Returns:
            QA_Position -- [description]
        """
        self.account_cookie = diff_slice['user_id']
        self.code = diff_slice['instrument_id']
        self.volume_long_today = diff_slice['volume_long_today']
        self.volume_long_his = diff_slice['volume_long_his']
        self.volume_long_frozen_today = diff_slice['volume_long_frozen_today']
        self.volume_long_frozen_his = diff_slice['volume_long_frozen_his']
        self.volume_short_today = diff_slice['volume_short_today']
        self.volume_short_his = diff_slice['volume_short_his']
        self.volume_short_frozen_today = diff_slice[
            'volume_short_frozen_today']
        self.volume_short_frozen_his = diff_slice['volume_short_frozen_his']
        self.open_price_long = diff_slice['open_price_long']
        self.open_price_short = diff_slice['open_price_short']
        self.open_cost_long = diff_slice['open_cost_long']
        self.open_cost_short = diff_slice['open_cost_short']
        self.position_price_long = diff_slice['position_price_long']
        self.position_price_short = diff_slice['position_price_short']
        self.position_cost_long = diff_slice['position_cost_long']
        self.position_cost_short = diff_slice['position_cost_short']
        self.margin_long = diff_slice['margin_long']
        self.margin_short = diff_slice['margin_short']
        self.exchange_id = diff_slice['exchange_id']
        self.market_type = MARKET_TYPE.FUTURE_CN
        return self

    @property
    def volume_long(self):
        return self.volume_long_today + self.volume_long_his

    @property
    def volume_short(self):
        return self.volume_short_his + self.volume_short_today

    @property
    def volume_long_frozen(self):
        return self.volume_long_frozen_his + self.volume_long_frozen_today

    @property
    def volume_short_frozen(self):
        return self.volume_short_frozen_his + self.volume_short_frozen_today

    @property
    def margin(self):
        return self.margin_long + self.margin_short

    @property
    def float_profit_long(self):
        if self.market_preset is not None:
            return self.last_price * self.volume_long * self.market_preset.get(
                'unit_table', 1) - self.open_cost_long

    @property
    def float_profit_short(self):
        if self.market_preset is not None:
            return self.open_cost_short - self.last_price * self.volume_short * self.market_preset.get(
                'unit_table', 1)

    @property
    def float_profit(self):
        return self.float_profit_long + self.float_profit_short

    @property
    def position_profit_long(self):
        if self.market_preset is not None:
            return self.last_price * self.volume_long * self.market_preset.get(
                'unit_table', 1) - self.position_cost_long

    @property
    def position_profit_short(self):
        if self.market_preset is not None:
            return self.position_cost_short - self.last_price * self.volume_short * self.market_preset.get(
                'unit_table', 1)

    @property
    def position_profit(self):
        return self.position_profit_long + self.position_profit_short

    @property
    def static_message(self):
        return {
            # 基础字段
            'code': self.code,  # 品种名称
            'instrument_id': self.code,
            'user_id': self.account_cookie,
            'portfolio_cookie': self.portfolio_cookie,
            'username': self.username,
            'position_id': self.position_id,
            'account_cookie': self.account_cookie,
            'frozen': self.frozen,
            'name': self.name,
            'spms_id': self.spms_id,
            'oms_id': self.oms_id,
            'market_type': self.market_type,
            'exchange_id': self.exchange_id,  # 交易所ID
            'moneypreset': self.moneypreset,
            'moneypresetLeft': self.moneypresetLeft,
            'lastupdatetime': str(self.time),
            # 持仓量
            'volume_long_today': self.volume_long_today,
            'volume_long_his': self.volume_long_his,
            'volume_long': self.volume_long,
            'volume_short_today': self.volume_short_today,
            'volume_short_his': self.volume_short_his,
            'volume_short': self.volume_short,
            # 平仓委托冻结(未成交)
            'volume_long_frozen_today': self.volume_long_frozen_today,
            'volume_long_frozen_his': self.volume_long_frozen_his,
            'volume_long_frozen': self.volume_long_frozen,
            'volume_short_frozen_today': self.volume_short_frozen_today,
            'volume_short_frozen_his': self.volume_short_frozen_his,
            'volume_short_frozen': self.volume_short_frozen,
            # 保证金
            'margin_long': self.margin_long,  # 多头保证金
            'margin_short': self.margin_short,
            'margin': self.margin,
            # 持仓字段
            'position_price_long': self.position_price_long,  # 多头成本价
            'position_cost_long': self.position_cost_long,  # 多头总成本(  总市值)
            'position_price_short': self.position_price_short,
            'position_cost_short': self.position_cost_short,
            # 平仓字段
            'open_price_long': self.open_price_long,  # 多头开仓价
            'open_cost_long': self.open_cost_long,  # 多头开仓成本
            'open_price_short': self.open_price_short,  # 空头开仓价
            'open_cost_short': self.open_cost_short,  # 空头成本
            # 历史字段
            'trades': self.trades,
            'orders': self.orders
        }

    @property
    def hold_detail(self):
        return {
            # 持仓量
            'volume_long_today': self.volume_long_today,
            'volume_long_his': self.volume_long_his,
            'volume_long': self.volume_long,
            'volume_short_today': self.volume_short_today,
            'volume_short_his': self.volume_short_his,
            'volume_short': self.volume_short
        }

    @property
    def realtime_message(self):
        return {
            # 扩展字段
            "last_price": self.last_price,
            # //多头浮动盈亏  ps.last_price * ps.volume_long * ps.ins->volume_multiple - ps.open_cost_long;
            "float_profit_long": self.float_profit_long,
            # //空头浮动盈亏  ps.open_cost_short - ps.last_price * ps.volume_short * ps.ins->volume_multiple;
            "float_profit_short": self.float_profit_short,
            # //浮动盈亏 = float_profit_long + float_profit_short
            "float_profit": self.float_profit,
            "position_profit_long": self.position_profit_long,  # //多头持仓盈亏
            "position_profit_short": self.position_profit_short,  # //空头持仓盈亏
            # //持仓盈亏 = position_profit_long + position_profit_short
            "position_profit": self.position_profit
        }

    @property
    def message(self):
        msg = self.static_message
        msg.update(self.realtime_message)
        return msg

    def order_check(self, amount: float, price: float, towards: int,
                    order_id: str) -> bool:
        res = False
        if towards == ORDER_DIRECTION.BUY_CLOSE:
            # print('buyclose')
            #print(self.volume_short - self.volume_short_frozen)
            # print(amount)
            if (self.volume_short - self.volume_short_frozen) >= amount:
                # check
                self.volume_short_frozen_today += amount
                res = True
            else:
                print('BUYCLOSE 仓位不足')

        elif towards == ORDER_DIRECTION.BUY_CLOSETODAY:
            if (self.volume_short_today -
                    self.volume_short_frozen_today) >= amount:
                self.volume_short_frozen_today += amount
                res = True
            else:
                print('BUYCLOSETODAY 今日仓位不足')
        elif towards == ORDER_DIRECTION.SELL_CLOSE:
            # print('sellclose')
            #print(self.volume_long - self.volume_long_frozen)
            # print(amount)
            if (self.volume_long - self.volume_long_frozen) >= amount:
                self.volume_long_frozen_today += amount
                res = True
            else:
                print('SELL CLOSE 仓位不足')

        elif towards == ORDER_DIRECTION.SELL_CLOSETODAY:
            if (self.volume_long_today -
                    self.volume_short_frozen_today) >= amount:
                # print('sellclosetoday')
                #print(self.volume_long_today - self.volume_long_frozen)
                # print(amount)
                self.volume_long_frozen_today += amount
                return True
            else:
                print('SELLCLOSETODAY 今日仓位不足')
        elif towards in [
                ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN,
                ORDER_DIRECTION.BUY
        ]:
            """
            冻结的保证金
            """
            moneyneed = float(amount) * float(price) * float(
                self.market_preset.get('unit_table', 1)) * float(
                    self.market_preset.get('buy_frozen_coeff', 1))

            if (self.moneypresetLeft > moneyneed) or self.allow_exceed:
                self.moneypresetLeft -= moneyneed
                self.frozen[order_id] = moneyneed
                res = True
            else:
                print('开仓保证金不足 TOWARDS{} Need{} HAVE{}'.format(
                    towards, moneyneed, self.moneypresetLeft))

        return res

    def send_order(self, amount: float, price: float, towards: int):
        order_id = str(uuid.uuid4())
        if self.order_check(amount, price, towards, order_id):
            #print('order check success')
            order = {
                'position_id': str(self.position_id),
                'account_cookie': self.account_cookie,
                'instrument_id': self.code,
                'towards': int(towards),
                'exchange_id': str(self.exchange_id),
                'order_time': str(self.time),
                'volume': float(amount),
                'price': float(price),
                'order_id': order_id,
                'status': ORDER_STATUS.NEW
            }
            self.orders[order_id] = order
            return order
        else:
            print(RuntimeError('ORDER CHECK FALSE: {}'.format(self.code)))
            return False

    def update_pos(self, price, amount, towards):
        """支持股票/期货的更新仓位

        Arguments:
            price {[type]} -- [description]
            amount {[type]} -- [description]
            towards {[type]} -- [description]

            margin: 30080
            margin_long: 0
            margin_short: 30080
            open_cost_long: 0
            open_cost_short: 419100
            open_price_long: 4193
            open_price_short: 4191
            position_cost_long: 0
            position_cost_short: 419100
            position_price_long: 4193
            position_price_short: 4191
            position_profit: -200
            position_profit_long: 0
            position_profit_short: -200
        """
        self.on_pirce_change(price)
        temp_cost = float(amount)*float(price) * \
            float(self.market_preset.get('unit_table', 1))
        profit = 0
        if towards == ORDER_DIRECTION.BUY:
            # 股票模式/ 期货买入开仓
            marginValue = temp_cost
            self.margin_long += marginValue
            # 重算开仓均价
            self.open_price_long = (self.open_price_long * self.volume_long +
                                    amount * price) / (amount +
                                                       self.volume_long)
            # 重算持仓均价
            self.position_price_long = (
                self.position_price_long * self.volume_long +
                amount * price) / (amount + self.volume_long)
            # 增加今仓数量 ==> 会自动增加volume_long
            self.volume_long_today += amount
            #
            self.open_cost_long += temp_cost
            self.position_cost_long += temp_cost

        elif towards == ORDER_DIRECTION.SELL:
            # 股票卖出模式:
            # 今日买入仓位不能卖出
            if self.volume_long_his > amount:

                self.position_cost_long = self.position_cost_long * \
                    (self.volume_long - amount)/self.volume_long
                self.open_cost_long = self.open_cost_long * \
                    (self.volume_long-amount)/self.volume_long

                self.volume_long_his -= amount

                self.volume_long_frozen_today -= amount
                marginValue = -1 * (self.position_price_long * amount)
                profit = (price - self.position_price_long) * amount
                self.moneypresetLeft += (-marginValue + profit)

        elif towards == ORDER_DIRECTION.BUY_OPEN:

            # 增加保证金
            marginValue = temp_cost * \
                self.market_preset['buy_frozen_coeff']
            self.margin_long += marginValue
            # 重算开仓均价
            self.open_price_long = (self.open_price_long * self.volume_long +
                                    amount * price) / (amount +
                                                       self.volume_long)
            # 重算持仓均价
            self.position_price_long = (
                self.position_price_long * self.volume_long +
                amount * price) / (amount + self.volume_long)
            # 增加今仓数量 ==> 会自动增加volume_long
            self.volume_long_today += amount
            #
            self.open_cost_long += temp_cost
            self.position_cost_long += temp_cost
            self.moneypresetLeft -= marginValue

        elif towards == ORDER_DIRECTION.SELL_OPEN:
            # 增加保证金
            """
            1. 增加卖空保证金
            2. 重新计算 开仓成本
            3. 重新计算 持仓成本
            4. 增加开仓cost
            5. 增加持仓cost
            6. 增加空单仓位
            """
            marginValue = temp_cost * \
                self.market_preset['sell_frozen_coeff']
            self.margin_short += marginValue
            # 重新计算开仓/持仓成本
            self.open_price_short = (
                self.open_price_short * self.volume_short +
                amount * price) / (amount + self.volume_short)
            self.position_price_short = (
                self.position_price_short * self.volume_short +
                amount * price) / (amount + self.volume_short)
            self.open_cost_short += temp_cost
            self.position_cost_short += temp_cost
            self.volume_short_today += amount
            self.moneypresetLeft -= marginValue

        elif towards == ORDER_DIRECTION.BUY_CLOSETODAY:
            if self.volume_short_today > amount:
                self.position_cost_short = self.position_cost_short * \
                    (self.volume_short-amount)/self.volume_short
                self.open_cost_short = self.open_cost_short * \
                    (self.volume_short-amount)/self.volume_short
                self.volume_short_today -= amount
                self.volume_short_frozen_today += amount
                # close_profit = (self.position_price_short - price) * volume * position->ins->volume_multiple;
                marginValue = -(self.position_price_short * amount*self.market_preset.get('unit_table') *\
                    self.market_preset['sell_frozen_coeff'])
                profit = (self.position_price_short - price
                          ) * amount * self.market_preset.get('unit_table')

                self.moneypresetLeft += (-marginValue + profit)

                # 释放保证金
                # TODO
                # self.margin_short
                #self.open_cost_short = price* amount

        elif towards == ORDER_DIRECTION.SELL_CLOSETODAY:
            if self.volume_long_today > amount:
                self.position_cost_long = self.position_cost_long * \
                    (self.volume_long - amount)/self.volume_long
                self.open_cost_long = self.open_cost_long * \
                    (self.volume_long-amount)/self.volume_long
                self.volume_long_today -= amount
                self.volume_long_frozen_today += amount

                marginValue = -1*(self.position_price_long * amount*self.market_preset.get('unit_table') *\
                    self.market_preset['buy_frozen_coeff'])
                profit = (price - self.position_price_long) * \
                    amount * self.market_preset.get('unit_table')
                self.moneypresetLeft += (-marginValue + profit)

        elif towards == ORDER_DIRECTION.BUY_CLOSE:
            # 有昨仓先平昨仓
            self.position_cost_short = self.position_cost_short * \
                (self.volume_short-amount)/self.volume_short
            self.open_cost_short = self.open_cost_short * \
                (self.volume_short-amount)/self.volume_short
            if self.volume_short_his >= amount:
                self.volume_short_his -= amount
            else:
                self.volume_short_today -= (amount - self.volume_short_his)
                self.volume_short_his = 0
            self.volume_short_frozen_today -= amount

            marginValue = -1*(self.position_price_short * amount*self.market_preset.get('unit_table') *\
                self.market_preset['sell_frozen_coeff'])
            profit = (self.position_price_short -
                      price) * amount * self.market_preset.get('unit_table')

            self.moneypresetLeft += (-marginValue + profit)
        elif towards == ORDER_DIRECTION.SELL_CLOSE:
            # 有昨仓先平昨仓
            self.position_cost_long = self.position_cost_long * \
                (self.volume_long - amount)/self.volume_long
            self.open_cost_long = self.open_cost_long * \
                (self.volume_long-amount)/self.volume_long
            if self.volume_long_his >= amount:
                self.volume_long_his -= amount
            else:
                self.volume_long_today -= (amount - self.volume_long_his)
                self.volume_long_his = 0
            self.volume_long_frozen_today -= amount
            marginValue = -1*(self.position_price_long * amount*self.market_preset.get('unit_table') *\
                self.market_preset['buy_frozen_coeff'])
            profit = (price - self.position_price_long) * \
                amount * self.market_preset.get('unit_table')
            self.moneypresetLeft += (-marginValue + profit)
        # 计算收益/成本

        return marginValue, profit

    def settle(self):
        """收盘后的结算事件
        """
        self.volume_long_his += self.volume_long_today
        self.volume_long_today = 0
        self.volume_long_frozen_today = 0
        self.volume_short_his += self.volume_short_today
        self.volume_short_today = 0
        self.volume_short_frozen_today = 0

    @property
    def curpos(self):
        return {
            'volume_long': self.volume_long,
            'volume_short': self.volume_short,
            'volume_long_frozen': self.volume_long_frozen,
            'volume_short_frozen': self.volume_short_frozen
        }

    @property
    def close_available(self):
        """可平仓数量

        Returns:
            [type] -- [description]
        """
        return {
            'volume_long': self.volume_long - self.volume_long_frozen,
            'volume_short': self.volume_short - self.volume_short_frozen
        }

    def change_moneypreset(self, money):
        self.moneypreset = money

    def save(self):
        """save&update

        save data to mongodb | update
        """
        print(self.static_message)
        save_position(self.static_message)

    def reload(self):
        res = DATABASE.positions.find_one({
            'account_cookie': self.account_cookie,
            'portfolio_cookie': self.portfolio_cookie,
            'username': self.username,
            'position_id': self.position_id
        })
        if res is None:
            self.save()
        else:
            self.loadfrommessage(res)

    def calc_commission(
        self,
        trade_price,
        trade_amount,
        trade_towards,
    ):
        if self.market_type == MARKET_TYPE.FUTURE_CN:
            # 期货不收税
            # 双边手续费 也没有最小手续费限制
            value = trade_price * trade_amount * \
                self.market_preset.get_unit(code)

            commission_fee_preset = self.market_preset.get_code(code)
            if trade_towards in [
                    ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.BUY_CLOSE,
                    ORDER_DIRECTION.SELL_CLOSE, ORDER_DIRECTION.SELL_OPEN
            ]:
                commission_fee = commission_fee_preset['commission_coeff_pervol'] * trade_amount + \
                    commission_fee_preset['commission_coeff_peramount'] * \
                    abs(value)
            elif trade_towards in [
                    ORDER_DIRECTION.BUY_CLOSETODAY,
                    ORDER_DIRECTION.SELL_CLOSETODAY
            ]:
                commission_fee = commission_fee_preset['commission_coeff_today_pervol'] * trade_amount + \
                    commission_fee_preset['commission_coeff_today_peramount'] * \
                    abs(value)
            return commission_fee

    def loadfrommessage(self, message):
        self.__init__(
            code=message['code'],
            account_cookie=message['account_cookie'],
            frozen=message['frozen'],
            portfolio_cookie=message['portfolio_cookie'],
            username=message['username'],
            moneypreset=message['moneypreset'],  # 初始分配资金
            moneypresetLeft=message['moneypresetLeft'],
            volume_long_today=message['volume_long_today'],
            volume_long_his=message['volume_long_his'],
            volume_short_today=message['volume_short_today'],
            volume_short_his=message['volume_short_his'],
            volume_long_frozen_his=message['volume_long_frozen_his'],
            volume_long_frozen_today=message['volume_long_frozen_today'],
            volume_short_frozen_his=message['volume_short_frozen_his'],
            volume_short_frozen_today=message['volume_short_frozen_today'],
            margin_long=message['margin_long'],
            margin_short=message['margin_short'],
            open_price_long=message['open_price_long'],
            open_price_short=message['open_price_short'],
            # 逐日盯市的前一交易日的结算价
            position_price_long=message['position_price_long'],
            # 逐日盯市的前一交易日的结算价
            position_price_short=message['position_price_short'],
            open_cost_long=message['open_cost_long'],
            open_cost_short=message['open_cost_short'],
            position_cost_long=message['position_cost_long'],
            position_cost_short=message['position_cost_short'],
            position_id=message['position_id'],
            market_type=message['market_type'],
            exchange_id=message['exchange_id'],
            trades=message['trades'],
            orders=message['orders'],
            commission=message['commission'],
            name=message['name'])

        return self

    def on_order(self, order: QA_Order):
        """这里是一些外部操作导致的POS变化

        - 交易过程的外部手动交易
        - 风控状态下的监控外部交易

        order_id 是外部的
        trade_id 不一定存在
        """

        if order['order_id'] not in self.frozen.keys():
            print('OUTSIDE ORDER')
            # self.frozen[order['order_id']] = order[]
            # 回放订单/注册进订单系统
            order = self.send_order(
                order.get('amount', order.get('volume')), order['price'],
                eval('ORDER_DIRECTION.{}_{}'.format(order.get('direction'),
                                                    order.get('offset'))))
            self.orders[order]['status'] = ORDER_STATUS.QUEUED

    def on_transaction(self, transaction: dict):
        towards = transaction.get(
            'towards',
            eval('ORDER_DIRECTION.{}_{}'.format(transaction.get('direction'),
                                                transaction.get('offset'))))
        transaction['towards'] = towards
        # TODO:
        # 在这里可以加入更多关于PMS交易的代码
        try:
            self.update_pos(
                transaction['price'],
                transaction.get('amount', transaction.get('volume')), towards)
            self.moneypresetLeft += self.frozen.get(transaction['order_id'], 0)
            # 当出现外部交易的时候, 直接在frozen中注册订单
            self.frozen[transaction['order_id']] = 0
            self.orders[transaction['order_id']] = ORDER_STATUS.SUCCESS_ALL
            self.trades.append(transaction)
        except Exception as e:
            raise e

    def on_pirce_change(self, price):
        self.last_price = price

    def on_bar(self, bar):
        """只订阅这个code的数据

        Arguments:
            bar {[type]} -- [description]
        """
        self.last_price = bar['close']
        # print(self.realtime_message)
        pass

    def on_tick(self, tick):
        """只订阅当前code的tick

        Arguments:
            tick {[type]} -- [description]
        """
        self.last_price = tick['LastPrice']
        # print(self.realtime_message)
        pass

    def on_signal(self, signal):
        raise NotImplementedError('此接口为内部接口 为CEP专用')

    def callback_sub(self):
        raise NotImplementedError('此接口为内部接口 为CEP专用')

    def callback_pub(self):
        raise NotImplementedError('此接口为内部接口 为CEP专用')
示例#3
0
class QA_Account(QA_Worker):
    """QA_Account
    User-->Portfolio-->Account/Strategy

    :::::::::::::::::::::::::::::::::::::::::::::::::
    ::        :: Portfolio 1 -- Account/Strategy 1 ::
    ::  USER  ::             -- Account/Strategy 2 ::
    ::        :: Portfolio 2 -- Account/Strategy 3 ::
    :::::::::::::::::::::::::::::::::::::::::::::::::

    2018/1/5 再次修改 改版本去掉了多余的计算 精简账户更新
    ======================

    - 不再计算总资产/不再计算当前持仓/不再计算交易对照明细表
    - 不再动态计算账户股票/期货市值
    - 只维护 cash/history两个字段 剩下的全部惰性计算


    QA_Account 是QUANTAXIS的最小不可分割单元之一

    QA_Account是账户类 需要兼容股票/期货/指数
    QA_Account继承自QA_Worker 可以被事件驱动
    QA_Account可以直接被QA_Strategy继承

    有三类输入:
    信息类: 账户绑定的策略名/账户的用户名/账户类别/账户识别码/账户的broker
    资产类: 现金/可用现金/交易历史/交易对照表
    规则类: 是否允许卖空/是否允许t0结算

    方法:
    惰性计算:最新持仓/最新总资产/最新现金/持仓面板
    生成订单/接受交易结果数据
    接收新的数据/on_bar/on_tick方法/缓存新数据的market_data

    @royburns  1.添加注释
    2018/05/18

    T0交易的sell_available和正常的sell_available不一样:

    T0交易中, 当买入一笔/卖出一笔, 当天操作额度都会下降

    T0的订单-账户对应系统


    @2018/06/11
    QA_Account不会基于行情计算市值,因此都只会对应记录证券数量和现金资产




    @2018/12/23
    当我们继承/复用 QA_Account 的时候, 我们需要实现什么

    - init_cash
    - init_hold
    - broker



    @2018/12/24
    账户需要追踪

    @2018/12/31
    账户需要截面数据 
    需要在任意截面的基础上往下做

    基础的信息截面 ==>  init_cash/init_hold |  history的初始值

    任意时间点的信息截面

    """
    def __init__(self,
                 strategy_name=None,
                 user_cookie=None,
                 portfolio_cookie=None,
                 account_cookie=None,
                 market_type=MARKET_TYPE.STOCK_CN,
                 frequence=FREQUENCE.DAY,
                 broker=BROKER_TYPE.BACKETEST,
                 init_hold={},
                 init_cash=1000000,
                 commission_coeff=0.00025,
                 tax_coeff=0.001,
                 margin_level={},
                 allow_t0=False,
                 allow_sellopen=False,
                 allow_margin=False,
                 running_environment=RUNNING_ENVIRONMENT.BACKETEST):
        """

        :param [str] strategy_name:  策略名称
        :param [str] user_cookie:   用户cookie
        :param [str] portfolio_cookie: 组合cookie
        :param [str] account_cookie:   账户cookie

        :param [dict] init_hold         初始化时的股票资产
        :param [float] init_cash:         初始化资金
        :param [float] commission_coeff:  交易佣金 :默认 万2.5   float 类型
        :param [float] tax_coeff:         印花税   :默认 千1.5   float 类型

        :param [Bool] margin_level:      保证金比例 默认{}
        :param [Bool] allow_t0:          是否允许t+0交易  默认False
        :param [Bool] allow_sellopen:    是否允许卖空开仓  默认False
        :param [Bool] allow_margin:      是否允许保证金交易 默认False

        ### 注意
        >>>>>>>>>>>>>
        在期货账户中:
        allow_t0/ allow_sellopen 是必须打开的

        allow_margin 是作为保证金账户的开关 默认关闭 可以打开 则按照market_preset中的保证金比例来计算
        具体可以参见: https://github.com/QUANTAXIS/QUANTAXIS/blob/master/EXAMPLE/test_backtest/FUTURE/TEST_%E4%BF%9D%E8%AF%81%E9%87%91%E8%B4%A6%E6%88%B7.ipynb

        >>>>>>>>>>>>>



        :param [QA.PARAM] market_type:   市场类别 默认QA.MARKET_TYPE.STOCK_CN A股股票
        :param [QA.PARAM] frequence:     账户级别 默认日线QA.FREQUENCE.DAY
        :param [QA.PARAM] broker:        BROEKR类 默认回测 QA.BROKER_TYPE.BACKTEST
        :param [QA.PARAM] running_environment 当前运行环境 默认Backtest

        # 2018/06/11 init_assets 从float变为dict,并且不作为输入,作为只读属性
        #  :param [float] init_assets:       初始资产  默认 1000000 元 (100万)
        init_assets:{
            cash: xxx,
            stock: {'000001':2000},
            init_date: '2018-02-05',
            init_datetime: '2018-02-05 15:00:00'
        }
        # 2018/06/11 取消在初始化的时候的cash和history输入
        # :param [list] cash:              可用现金  默认 是 初始资产  list 类型
        # :param [list] history:           交易历史


        # 2018/11/9 修改保证金交易

        # 我们把冻结的保证金 看做是未来的已实现交易:
        # 如==> 当前的一手空单 认为是未来的卖出成交(已知价格 不知时间)
        # 因此我们如此对于保证金交易进行评估:
        # 账户买入:
        多单开仓:  cash 下降x 保证金增加x 增加一手未来的卖出合约(持仓)  ==> 平仓: cash上升 保证金恢复
        cash + frozen(平仓释放) + 未平仓位

        cash, available_cash

        frozen{
                RB1901: {
                        towards 2: {avg_money : xxx, amount: xxx, queue: collection.deque()},
                        towards -2: {avg_money, amount, queue: collection.deque()}
                        },
                IF1901: {
                        towards 2: {avg_money, amount,queue: collection.deque()},
                        towards -2: {avg_money, amount,queue: collection.deque()}
                }
            }
        }

        hold: {
            RB1901: {
                1, amount, # 多单待平仓
                -1, amount # 空单待平仓
            }
        }
        """
        super().__init__()
        # warnings.warn('QUANTAXIS 1.0.46 has changed the init_assets ==> init_cash, please pay attention to this change if you using init_cash to initial an account class,\
        #         ', DeprecationWarning, stacklevel=2)
        self._history_headers = [
            'datetime', 'code', 'price', 'amount', 'cash', 'order_id',
            'realorder_id', 'trade_id', 'account_cookie', 'commission', 'tax',
            'message', 'frozen'
        ]
        ########################################################################
        # 信息类:
        self.strategy_name = strategy_name
        self.user_cookie = user_cookie
        self.portfolio_cookie = portfolio_cookie
        self.account_cookie = QA_util_random_with_topic(
            'Acc') if account_cookie is None else account_cookie

        self.market_type = market_type
        self.broker = broker
        self.frequence = frequence
        self.running_environment = running_environment
        ########################################################################
        self._market_data = None
        self._currenttime = None
        self.commission_coeff = commission_coeff
        self.tax_coeff = tax_coeff
        self.datetime = None
        self.running_time = datetime.datetime.now()
        self.quantaxis_version = __version__
        ########################################################################
        # 资产类
        self.orders = QA_OrderQueue()  # 历史委托单
        self.init_cash = init_cash
        self.init_hold = pd.Series(init_hold, name='amount') if isinstance(
            init_hold, dict) else init_hold
        self.init_hold.index.name = 'code'
        self.cash = [self.init_cash]
        self.cash_available = self.cash[-1]  # 可用资金
        self.sell_available = copy.deepcopy(self.init_hold)
        self.buy_available = copy.deepcopy(self.init_hold)
        self.history = []
        self.time_index = []

        # 在回测中, 每日结算后更新
        # 真实交易中, 为每日初始化/每次重新登录后的同步信息
        self.static_balance = {
            'static_assets': [],
            'cash': [],
            'frozen': [],
            'hold': [],
            'date': []
        }  # 日结算
        self.today_trade = {'last': [], 'current': []}
        self.today_orders = {'last': [], 'current': []}

        ########################################################################
        # 规则类
        # 1.是否允许t+0 及买入及结算
        # 2.是否允许卖空开仓
        # 3.是否允许保证金交易/ 如果不是false 就需要制定保证金比例(dict形式)

        # 期货: allow_t0 True allow_sellopen True
        #

        self.allow_t0 = allow_t0
        self.allow_sellopen = allow_sellopen
        self.allow_margin = allow_margin
        self.margin_level = margin_level  # 保证金比例

        if self.market_type is MARKET_TYPE.FUTURE_CN:
            self.allow_t0 = True
            self.allow_sellopen = True

        if self.allow_t0 and self.allow_sellopen or self.market_type is MARKET_TYPE.FUTURE_CN:
            self.load_marketpreset()
        """期货的多开/空开 ==> 资金冻结进保证金  frozen

        对应平仓的时候, 释放保证金

        1. frozen  是一个dict :   {[code]:queue}
            key是标的 value是对应的交易queue

        """

        self.frozen = {}  # 冻结资金(保证金)

    def __repr__(self):
        return '< QA_Account {}>'.format(self.account_cookie)

    @property
    def message(self):
        'the standard message which can be transfer'
        return {
            'source':
            'account',
            'account_cookie':
            self.account_cookie,
            'portfolio_cookie':
            self.portfolio_cookie,
            'user_cookie':
            self.user_cookie,
            'broker':
            self.broker,
            'market_type':
            self.market_type,
            'strategy_name':
            self.strategy_name,
            'current_time':
            str(self._currenttime),
            'allow_sellopen':
            self.allow_sellopen,
            'allow_t0':
            self.allow_t0,
            'margin_level':
            self.margin_level,
            'init_assets':
            self.init_assets,
            'init_cash':
            self.init_cash,
            'init_hold':
            self.init_hold.to_dict(),
            'commission_coeff':
            self.commission_coeff,
            'tax_coeff':
            self.tax_coeff,
            'cash':
            self.cash,
            'history':
            self.history,
            'trade_index':
            self.time_index,
            'running_time':
            str(datetime.datetime.now())
            if self.running_time is None else str(self.running_time),
            'quantaxis_version':
            self.quantaxis_version,
            'running_environment':
            self.running_environment,
            'start_date':
            self.start_date,
            'end_date':
            self.end_date
        }

    def load_marketpreset(self):
        """加载市场表
        """

        self.market_preset = MARKET_PRESET()

    @property
    def init_hold_with_account(self):
        """带account_id的初始化持仓

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

        return self.init_hold.reset_index().assign(
            account_cookie=self.account_cookie).set_index(
                ['code', 'account_cookie'])

    @property
    def init_assets(self):
        """初始化账户资产

        Returns:
            dict -- 2keys-cash,hold
        """

        return {'cash': self.init_cash, 'hold': self.init_hold.to_dict()}

    @property
    def code(self):
        """
        该账户曾交易代码 用set 去重
        """
        return list(set([item[1] for item in self.history]))

    @property
    def date(self):
        """账户运行的日期

        Arguments:
            self {[type]} -- [description]

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

        if self.datetime is not None:
            return str(self.datetime)[0:10]
        else:
            return None

    @property
    def poisitions(self):
        raise NotImplementedError

    @property
    def start_date(self):
        """账户的起始交易日期(只在回测中使用)

        Raises:
            RuntimeWarning -- [description]

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

        if len(self.time_index) > 0:
            return str(min(self.time_index))[0:10]
        else:
            print(
                RuntimeWarning(
                    'QAACCOUNT: THIS ACCOUNT DOESNOT HAVE ANY TRADE'))

    @property
    def end_date(self):
        """账户的交易结束日期(只在回测中使用)

        Raises:
            RuntimeWarning -- [description]

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

        if len(self.time_index) > 0:
            return str(max(self.time_index))[0:10]
        else:
            print(
                RuntimeWarning(
                    'QAACCOUNT: THIS ACCOUNT DOESNOT HAVE ANY TRADE'))

    @property
    def market_data(self):
        return self._market_data

    @property
    def trade_range(self):
        return QA_util_get_trade_range(self.start_date, self.end_date)

    @property
    def trade_day(self):
        return list(
            pd.Series(self.time_index).apply(lambda x: str(x)[0:10]).unique())

    @property
    def history_table(self):
        '交易历史的table'
        if len(self.history) > 0:
            lens = len(self.history[0])
        else:
            lens = len(self._history_headers)

        return pd.DataFrame(data=self.history,
                            columns=self._history_headers[:lens]).sort_index()

    @property
    def today_trade_table(self):
        return pd.DataFrame(data=self.today_trade['current'],
                            columns=self._history_headers).sort_index()

    @property
    def cash_table(self):
        '现金的table'
        _cash = pd.DataFrame(data=[self.cash[1::], self.time_index],
                             index=['cash', 'datetime']).T
        _cash = _cash.assign(date=_cash.datetime.apply(
            lambda x: pd.to_datetime(str(x)[0:10]))).assign(
                account_cookie=self.account_cookie)  # .sort_values('datetime')
        return _cash.set_index(['datetime', 'account_cookie'], drop=False)
        """
        实验性质
        @2018-06-09

        # 对于账户持仓的分解

        1. 真实持仓hold:

        正常模式/TZero模式:
            hold = 历史持仓(init_hold)+ 初始化账户后发生的所有交易导致的持仓(hold_available)

        动态持仓(初始化账户后的持仓)hold_available:
            self.history 计算而得

        2. 账户的可卖额度(sell_available)

        正常模式:
            sell_available
                结算前: init_hold+ 买卖交易(卖-)
                结算后: init_hold+ 买卖交易(买+ 卖-)
        TZero模式:
            sell_available
                结算前: init_hold - 买卖交易占用的额度(abs(买+ 卖-))
                结算过程 是为了补平(等于让hold={})
                结算后: init_hold
        """

    @property
    def hold(self):
        """真实持仓
        """
        return pd.concat([self.init_hold,
                          self.hold_available]).groupby('code').sum().replace(
                              0, np.nan).dropna().sort_index()

    @property
    def hold_available_temp(self):
        """可用持仓
        """
        return self._table.groupby('code').amount.sum().replace(
            0, np.nan).dropna().sort_index()

    @property
    def hold_available(self):
        """可用持仓
        """
        return self.history_table.groupby('code').amount.sum().replace(
            0, np.nan).dropna().sort_index()

    # @property
    # def order_table(self):
    #     """return order trade list"""
    #     return self.orders.trade_list

    @property
    def trade(self):
        """每次交易的pivot表

        Returns:
            pd.DataFrame

            此处的pivot_table一定要用np.sum
        """

        return self.history_table.pivot_table(
            index=['datetime', 'account_cookie'],
            columns='code',
            values='amount',
            aggfunc=np.sum).fillna(0).sort_index()

    @property
    def daily_cash(self):
        '每日交易结算时的现金表'
        res = self.cash_table.drop_duplicates(subset='date', keep='last')

        return pd.concat([res.set_index('date'), pd.Series(data=None, index=pd.to_datetime(self.trade_range).set_names('date'), name='predrop')], axis=1)\
            .ffill().drop(['predrop'], axis=1).reset_index().set_index(['date', 'account_cookie'], drop=False).sort_index()

    @property
    def daily_hold(self):
        '每日交易结算时的持仓表'
        data = self.trade.cumsum()
        if len(data) < 1:
            return None
        else:
            # print(data.index.levels[0])
            data = data.assign(account_cookie=self.account_cookie).assign(
                date=pd.to_datetime(data.index.levels[0]).date)

            data.date = pd.to_datetime(data.date)
            data = data.set_index(['date', 'account_cookie'])
            res = data[~data.index.duplicated(keep='last')].sort_index()
            # 这里会导致股票停牌时的持仓也被计算 但是计算market_value的时候就没了
            return pd.concat([res.reset_index().set_index('date'), pd.Series(data=None, index=pd.to_datetime(self.trade_range).set_names('date'), name='predrop')], axis=1)\
                .ffill().drop(['predrop'], axis=1).reset_index().set_index(['date', 'account_cookie']).sort_index()

    # 计算assets的时候 需要一个market_data=QA.QA_fetch_stock_day_adv(list(data.columns),data.index[0],data.index[-1])
    # (market_data.to_qfq().pivot('close')*data).sum(axis=1)+user_cookie.get_account(a_1).daily_cash.set_index('date').cash

    @property
    def latest_cash(self):
        'return the lastest cash 可用资金'
        return self.cash[-1]

    @property
    def current_time(self):
        'return current time (in backtest/real environment)'
        return self._currenttime

    def hold_table(self, datetime=None):
        "到某一个时刻的持仓 如果给的是日期,则返回当日开盘前的持仓"
        if datetime is None:
            hold_available = self.history_table.set_index(
                'datetime').sort_index().groupby(
                    'code').amount.sum().sort_index()
        else:
            hold_available = self.history_table.set_index(
                'datetime').sort_index().loc[:datetime].groupby(
                    'code').amount.sum().sort_index()

        return pd.concat([self.init_hold, hold_available
                          ]).groupby('code').sum().sort_index().apply(
                              lambda x: x if x > 0 else None).dropna()

    def hold_price(self, datetime=None):
        """计算持仓成本  如果给的是日期,则返回当日开盘前的持仓

        Keyword Arguments:
            datetime {[type]} -- [description] (default: {None})

        Returns:
            [type] -- [description]
        """
        def weights(x):
            if sum(x['amount']) != 0:
                return np.average(x['price'],
                                  weights=x['amount'],
                                  returned=True)
            else:
                return np.nan

        if datetime is None:
            return self.history_table.set_index(
                'datetime', drop=False).sort_index().groupby('code').apply(
                    weights).dropna()
        else:
            return self.history_table.set_index(
                'datetime', drop=False).sort_index().loc[:datetime].groupby(
                    'code').apply(weights).dropna()

    # @property
    def hold_time(self, datetime=None):
        """持仓时间

        Keyword Arguments:
            datetime {[type]} -- [description] (default: {None})
        """
        def weights(x):
            if sum(x['amount']) != 0:
                return pd.Timestamp(self.datetime) - pd.to_datetime(
                    x.datetime.max())
            else:
                return np.nan

        if datetime is None:
            return self.history_table.set_index(
                'datetime', drop=False).sort_index().groupby('code').apply(
                    weights).dropna()
        else:
            return self.history_table.set_index(
                'datetime', drop=False).sort_index().loc[:datetime].groupby(
                    'code').apply(weights).dropna()

    def reset_assets(self, init_cash=None):
        'reset_history/cash/'
        self.sell_available = copy.deepcopy(self.init_hold)
        self.history = []
        self.init_cash = init_cash
        self.cash = [self.init_cash]
        self.cash_available = self.cash[-1]  # 在途资金

    def receive_simpledeal(self,
                           code,
                           trade_price,
                           trade_amount,
                           trade_towards,
                           trade_time,
                           message=None,
                           order_id=None,
                           trade_id=None,
                           realorder_id=None):
        """快速撮合成交接口


        此接口是一个直接可以成交的接口, 所以务必确保给出的信息是可以成交的

        此接口涉及的是
        1. 股票/期货的成交
        2. 历史记录的增加
        3. 现金/持仓/冻结资金的处理

        Arguments:
            code {[type]} -- [description]
            trade_price {[type]} -- [description]
            trade_amount {[type]} -- [description]
            trade_towards {[type]} -- [description]
            trade_time {[type]} -- [description]

        Keyword Arguments:
            message {[type]} -- [description] (default: {None})


        2018/11/7 @yutiansut
        修复一个bug: 在直接使用该快速撮合接口的时候, 期货卖出会扣减保证金, 买回来的时候应该反算利润

        如 3800卖空 3700买回平仓  应为100利润
        @2018-12-31 保证金账户ok


        @2019/1/3 一些重要的意思
        frozen = self.market_preset.get_frozen(code) # 保证金率
        unit = self.market_preset.get_unit(code)  # 合约乘数
        raw_trade_money = trade_price*trade_amount*market_towards  # 总市值
        value = raw_trade_money * unit  # 合约总价值
        trade_money = value * frozen    # 交易保证金
        """

        self.datetime = trade_time

        market_towards = 1 if trade_towards > 0 else -1
        # value 合约价值 unit 合约乘数
        if self.allow_margin:
            frozen = self.market_preset.get_frozen(code)  # 保证金率
            unit = self.market_preset.get_unit(code)  # 合约乘数
            raw_trade_money = trade_price * trade_amount * market_towards  # 总市值
            value = raw_trade_money * unit  # 合约总价值
            trade_money = value * frozen  # 交易保证金
        else:
            trade_money = trade_price * trade_amount * market_towards
            raw_trade_money = trade_money
            value = trade_money
            unit = 1
            frozen = 1
            # 计算费用
            # trade_price

        if self.market_type == MARKET_TYPE.FUTURE_CN:
            # 期货不收税
            # 双边手续费 也没有最小手续费限制

            commission_fee_preset = self.market_preset.get_code(code)
            if trade_towards in [
                    ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.BUY_CLOSE,
                    ORDER_DIRECTION.SELL_CLOSE, ORDER_DIRECTION.SELL_OPEN
            ]:
                commission_fee = commission_fee_preset['commission_coeff_pervol'] * trade_amount + \
                    commission_fee_preset['commission_coeff_peramount'] * \
                    abs(value)
            elif trade_towards in [
                    ORDER_DIRECTION.BUY_CLOSETODAY,
                    ORDER_DIRECTION.SELL_CLOSETODAY
            ]:
                commission_fee = commission_fee_preset['commission_coeff_today_pervol'] * trade_amount + \
                    commission_fee_preset['commission_coeff_today_peramount'] * \
                    abs(value)

            tax_fee = 0  # 买入不收印花税
        elif self.market_type == MARKET_TYPE.STOCK_CN:

            commission_fee = self.commission_coeff * \
                abs(trade_money)

            commission_fee = 5 if commission_fee < 5 else commission_fee
            if int(trade_towards) > 0:
                tax_fee = 0  # 买入不收印花税
            else:
                tax_fee = self.tax_coeff * abs(trade_money)

        # 结算交易
        if self.cash[-1] > trade_money + commission_fee + tax_fee:
            self.time_index.append(trade_time)
            # TODO: 目前还不支持期货的锁仓
            if self.allow_sellopen:
                if trade_towards in [
                        ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN
                ]:
                    # 开仓单占用现金 计算avg
                    # 初始化
                    if code in self.frozen.keys():
                        if trade_towards in self.frozen[code].keys():
                            pass
                        else:
                            self.frozen[code][trade_towards] = {
                                'money': 0,
                                'amount': 0,
                                'avg_price': 0
                            }
                    else:
                        self.frozen[code] = {
                            ORDER_DIRECTION.BUY_OPEN: {
                                'money': 0,
                                'amount': 0,
                                'avg_price': 0
                            },
                            ORDER_DIRECTION.SELL_OPEN: {
                                'money': 0,
                                'amount': 0,
                                'avg_price': 0
                            }
                        }
                    """[summary]
                    # frozen的计算
                    # money 冻结的资金
                    # amount  冻结的数量

                    2018-12-31                    

                    """

                    self.frozen[code][trade_towards]['money'] = (
                        (self.frozen[code][trade_towards]['money'] *
                         self.frozen[code][trade_towards]['amount']) +
                        abs(trade_money)) / (
                            self.frozen[code][trade_towards]['amount'] +
                            trade_amount)
                    self.frozen[code][trade_towards]['avg_price'] = (
                        (self.frozen[code][trade_towards]['avg_price'] *
                         self.frozen[code][trade_towards]['amount']) +
                        abs(raw_trade_money)) / (
                            self.frozen[code][trade_towards]['amount'] +
                            trade_amount)
                    self.frozen[code][trade_towards]['amount'] += trade_amount

                    self.cash.append(self.cash[-1] - abs(trade_money) -
                                     commission_fee - tax_fee)
                elif trade_towards in [
                        ORDER_DIRECTION.BUY_CLOSE, ORDER_DIRECTION.SELL_CLOSE
                ]:
                    # 平仓单释放现金
                    # if trade_towards == ORDER_DIRECTION.BUY_CLOSE:
                    # 卖空开仓 平仓买入
                    # self.cash
                    if trade_towards == ORDER_DIRECTION.BUY_CLOSE:  # 买入平仓  之前是空开
                        # self.frozen[code][ORDER_DIRECTION.SELL_OPEN]['money'] -= trade_money
                        self.frozen[code][ORDER_DIRECTION.
                                          SELL_OPEN]['amount'] -= trade_amount

                        frozen_part = self.frozen[code][
                            ORDER_DIRECTION.SELL_OPEN]['money'] * trade_amount
                        # 账户的现金+ 冻结的的释放 + 买卖价差* 杠杆
                        self.cash.append(self.cash[-1] + frozen_part +
                                         (frozen_part - trade_money) / frozen -
                                         commission_fee - tax_fee)
                        if self.frozen[code][
                                ORDER_DIRECTION.SELL_OPEN]['amount'] == 0:
                            self.frozen[code][
                                ORDER_DIRECTION.SELL_OPEN]['money'] = 0
                            self.frozen[code][
                                ORDER_DIRECTION.SELL_OPEN]['avg_price'] = 0

                    elif trade_towards == ORDER_DIRECTION.SELL_CLOSE:  # 卖出平仓  之前是多开
                        # self.frozen[code][ORDER_DIRECTION.BUY_OPEN]['money'] -= trade_money
                        self.frozen[code][
                            ORDER_DIRECTION.BUY_OPEN]['amount'] -= trade_amount

                        frozen_part = self.frozen[code][
                            ORDER_DIRECTION.BUY_OPEN]['money'] * trade_amount
                        self.cash.append(self.cash[-1] + frozen_part +
                                         (abs(trade_money) - frozen_part) /
                                         frozen - commission_fee - tax_fee)
                        if self.frozen[code][
                                ORDER_DIRECTION.BUY_OPEN]['amount'] == 0:
                            self.frozen[code][
                                ORDER_DIRECTION.BUY_OPEN]['money'] = 0
                            self.frozen[code][
                                ORDER_DIRECTION.BUY_OPEN]['avg_price'] = 0
            else:  # 不允许卖空开仓的==> 股票

                self.cash.append(self.cash[-1] - trade_money - tax_fee -
                                 commission_fee)
            if self.allow_t0 or trade_towards == ORDER_DIRECTION.SELL:
                self.sell_available[code] = self.sell_available.get(
                    code, 0) + trade_amount * market_towards
                self.buy_available = self.sell_available

            self.cash_available = self.cash[-1]
            frozen_money = abs(trade_money) if trade_towards in [
                ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN
            ] else 0
            self.history.append([
                trade_time, code, trade_price, market_towards * trade_amount,
                self.cash[-1], order_id, realorder_id, trade_id,
                self.account_cookie, commission_fee, tax_fee, message,
                frozen_money
            ])

        else:
            # print(self.cash[-1])
            self.cash_available = self.cash[-1]
            #print('NOT ENOUGH MONEY FOR {}'.format(order_id))

    def receive_deal(self,
                     code: str,
                     trade_id: str,
                     order_id: str,
                     realorder_id: str,
                     trade_price: float,
                     trade_amount: int,
                     trade_towards: int,
                     trade_time: str,
                     message=None):
        """更新deal

        Arguments:
            code {str} -- [description]
            trade_id {str} -- [description]
            order_id {str} -- [description]
            realorder_id {str} -- [description]
            trade_price {float} -- [description]
            trade_amount {int} -- [description]
            trade_towards {int} -- [description]
            trade_time {str} -- [description]

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

        print('receive deal')

        trade_time = str(trade_time)
        code = str(code)
        trade_price = float(trade_price)
        trade_towards = int(trade_towards)
        realorder_id = str(realorder_id)
        trade_id = str(trade_id)
        trade_amount = int(trade_amount)
        order_id = str(order_id)

        market_towards = 1 if trade_towards > 0 else -1
        """2019/01/03 直接使用快速撮合接口了
        2333 这两个接口现在也没啥区别了....
        太绝望了
        """

        self.receive_simpledeal(code,
                                trade_price,
                                trade_amount,
                                trade_towards,
                                trade_time,
                                message=message,
                                order_id=order_id,
                                trade_id=trade_id,
                                realorder_id=realorder_id)

    def send_order(self,
                   code=None,
                   amount=None,
                   time=None,
                   towards=None,
                   price=None,
                   money=None,
                   order_model=None,
                   amount_model=None,
                   *args,
                   **kwargs):
        """
        ATTENTION CHANGELOG 1.0.28
        修改了Account的send_order方法, 区分按数量下单和按金额下单两种方式

        - AMOUNT_MODEL.BY_PRICE ==> AMOUNT_MODEL.BY_MONEY # 按金额下单
        - AMOUNT_MODEL.BY_AMOUNT # 按数量下单

        在按金额下单的时候,应给予 money参数
        在按数量下单的时候,应给予 amount参数

        python code:
        Account=QA.QA_Account()

        Order_bymoney=Account.send_order(code='000001',
                                        price=11,
                                        money=0.3*Account.cash_available,
                                        time='2018-05-09',
                                        towards=QA.ORDER_DIRECTION.BUY,
                                        order_model=QA.ORDER_MODEL.MARKET,
                                        amount_model=QA.AMOUNT_MODEL.BY_MONEY
                                        )

        Order_byamount=Account.send_order(code='000001',
                                        price=11,
                                        amount=100,
                                        time='2018-05-09',
                                        towards=QA.ORDER_DIRECTION.BUY,
                                        order_model=QA.ORDER_MODEL.MARKET,
                                        amount_model=QA.AMOUNT_MODEL.BY_AMOUNT
                                        )

        :param code: 证券代码
        :param amount: 买卖 数量多数股
        :param time:  Timestamp 对象 下单时间
        :param towards: int , towards>0 买入 towards<0 卖出
        :param price: 买入,卖出 标的证券的价格
        :param money: 买卖 价格
        :param order_model: 类型 QA.ORDER_MODE
        :param amount_model:类型 QA.AMOUNT_MODEL
        :return:  QA_Order | False

        @2018/12/23
        send_order 是QA的标准返回, 如需对接其他接口, 只需要对于QA_Order做适配即可


        @2018/12/27
        在判断账户为期货账户(及 允许双向交易)

        @2018/12/30 保证金账户的修改
        1. 保证金账户冻结的金额
        2. 保证金账户的结算
        3. 保证金账户的判断

        """
        wrong_reason = None
        assert code is not None and time is not None and towards is not None and order_model is not None and amount_model is not None

        # 🛠todo 移到Utils类中,  时间转换
        # date 字符串 2011-10-11 长度10
        date = str(time)[0:10] if len(str(time)) == 19 else str(time)
        # time 字符串 20011-10-11 09:02:00  长度 19
        time = str(time) if len(str(time)) == 19 else '{} 09:31:00'.format(
            str(time)[0:10])

        # 🛠todo 移到Utils类中,  amount_to_money 成交量转金额
        # BY_MONEY :: amount --钱 如10000元  因此 by_money里面 需要指定价格,来计算实际的股票数
        # by_amount :: amount --股数 如10000股

        if self.allow_margin:
            amount = amount if amount_model is AMOUNT_MODEL.BY_AMOUNT else int(
                money / (self.market_preset.get_unit(code) *
                         self.market_preset.get_frozen(code) * price *
                         (1 + self.commission_coeff)) / 100) * 100
        else:

            amount = amount if amount_model is AMOUNT_MODEL.BY_AMOUNT else int(
                money / (price * (1 + self.commission_coeff)) / 100) * 100

        # 🛠todo 移到Utils类中,  money_to_amount 金额转成交量
        if self.allow_margin:
            money = amount * price * self.market_preset.get_unit(code)*self.market_preset.get_frozen(code) * \
                (1+self.commission_coeff) if amount_model is AMOUNT_MODEL.BY_AMOUNT else money
        else:
            money = amount * price * \
                (1+self.commission_coeff) if amount_model is AMOUNT_MODEL.BY_AMOUNT else money

        # flag 判断买卖 数量和价格以及买卖方向是否正确
        flag = False

        assert (int(towards) != 0)
        if int(towards) in [1, 2, 3]:
            # 是买入的情况(包括买入.买开.买平)
            if self.cash_available >= money:
                if self.market_type is MARKET_TYPE.STOCK_CN:  # 如果是股票 买入的时候有100股的最小限制
                    amount = int(amount / 100) * 100
                    self.cash_available -= money
                    flag = True

                if self.running_environment == RUNNING_ENVIRONMENT.TZERO:

                    if self.buy_available.get(code, 0) >= amount:
                        flag = True
                        self.cash_available -= money
                        self.buy_available[code] -= amount
                    else:
                        flag = False
                        wrong_reason = 'T0交易买入超出限额'

                if self.market_type == MARKET_TYPE.FUTURE_CN:
                    # 如果有负持仓-- 允许卖空的时候
                    if towards == 3:  # 多平
                        _hold = self.sell_available.get(code, 0)
                        # 假设有负持仓:
                        # amount为下单数量 如  账户原先-3手 现在平1手

                        #left_amount = amount+_hold if _hold < 0 else amount
                        _money = abs(
                            float(amount * price *
                                  (1 + self.commission_coeff)))

                        print(_hold)
                        if self.cash_available >= _money:
                            if _hold < 0:
                                self.cash_available -= _money

                                flag = True
                            else:
                                wrong_reason = '空单仓位不足'
                        else:
                            wrong_reason = '平多剩余资金不够'
                    if towards == 2:
                        self.cash_available -= money
                        flag = True
            else:
                wrong_reason = 'QAACCOUNT: 可用资金不足 cash_available {}  code {} time {} amount {} towards {}'.format(
                    self.cash_available, code, time, amount, towards)
        elif int(towards) in [-1, -2, -3]:
            # 是卖出的情况(包括卖出,卖出开仓allow_sellopen如果允许. 卖出平仓)
            # print(self.sell_available[code])
            _hold = self.sell_available.get(code, 0)  # _hold 是你的持仓

            # 如果你的hold> amount>0
            # 持仓数量>卖出数量
            if _hold >= amount:
                self.sell_available[code] -= amount
                # towards = ORDER_DIRECTION.SELL
                flag = True
            # 如果持仓数量<卖出数量
            else:

                # 如果是允许卖空开仓 实际计算时  先减去持仓(正持仓) 再计算 负持仓 就按原先的占用金额计算
                if self.allow_sellopen and towards == -2:

                    if self.cash_available >= money:  # 卖空的市值小于现金(有担保的卖空), 不允许裸卖空
                        # self.cash_available -= money
                        flag = True
                    else:
                        print('sellavailable', _hold)
                        print('amount', amount)
                        print('aqureMoney', money)
                        print('cash', self.cash_available)
                        wrong_reason = "卖空资金不足/不允许裸卖空"
                else:
                    wrong_reason = "卖出仓位不足"

        if flag and (amount > 0):
            _order = QA_Order(user_cookie=self.user_cookie,
                              strategy=self.strategy_name,
                              frequence=self.frequence,
                              account_cookie=self.account_cookie,
                              code=code,
                              market_type=self.market_type,
                              date=date,
                              datetime=time,
                              sending_time=time,
                              callback=self.receive_deal,
                              amount=amount,
                              price=price,
                              order_model=order_model,
                              towards=towards,
                              money=money,
                              amount_model=amount_model,
                              commission_coeff=self.commission_coeff,
                              tax_coeff=self.tax_coeff,
                              *args,
                              **kwargs)  # init
            # 历史委托order状态存储, 保存到 QA_Order 对象中的队列中
            self.datetime = time
            self.orders.insert_order(_order)
            return _order
        else:
            print('ERROR : CODE {} TIME {}  AMOUNT {} TOWARDS {}'.format(
                code, time, amount, towards))
            print(wrong_reason)
            return False

    def cancel_order(self, order):
        if order.towards in [
                ORDER_DIRECTION.BUY, ORDER_DIRECTION.BUY_OPEN,
                ORDER_DIRECTION.BUY_CLOSE
        ]:
            if order.amount_model is AMOUNT_MODEL.BY_MONEY:
                self.cash_available += order.money
            elif order.amount_model is AMOUNT_MODEL.BY_AMOUNT:
                self.cash_available += order.price * order.amount
        elif order.towards in [
                ORDER_DIRECTION.SELL, ORDER_DIRECTION.SELL_CLOSE,
                ORDER_DIRECTION.SELL_OPEN
        ]:
            self.sell_available[order.code] += order.amount

        # self.sell_available[]
    @property
    def close_positions_order(self):
        """平仓单

        Raises:
            RuntimeError -- if ACCOUNT.RUNNING_ENVIRONMENT is NOT TZERO

        Returns:
            list -- list with order
        """

        order_list = []
        time = '{} 15:00:00'.format(self.date)
        if self.running_environment == RUNNING_ENVIRONMENT.TZERO:
            for code, amount in self.hold_available.iteritems():
                order = False
                if amount < 0:
                    # 先卖出的单子 买平
                    order = self.send_order(
                        code=code,
                        price=0,
                        amount=abs(amount),
                        time=time,
                        towards=ORDER_DIRECTION.BUY_CLOSE,
                        order_model=ORDER_MODEL.CLOSE,
                        amount_model=AMOUNT_MODEL.BY_AMOUNT)
                elif amount > 0:
                    # 先买入的单子, 卖平
                    order = self.send_order(
                        code=code,
                        price=0,
                        amount=abs(amount),
                        time=time,
                        towards=ORDER_DIRECTION.SELL_CLOSE,
                        order_model=ORDER_MODEL.CLOSE,
                        amount_model=AMOUNT_MODEL.BY_AMOUNT)
                if order:
                    order_list.append(order)
            return order_list
        else:
            raise RuntimeError(
                'QAACCOUNT with {} environments cannot use this methods'.
                format(self.running_environment))

    def settle(self):
        """
        股票/期货的日结算

        股票的结算:  结转股票可卖额度
        T0的结算: 结转T0的额度

        期货的结算: 结转静态资金

        """

        if self.running_environment == RUNNING_ENVIRONMENT.TZERO and self.hold_available.sum(
        ) != 0:
            raise RuntimeError('QAACCOUNT: 该T0账户未当日仓位,请平仓 {}'.format(
                self.hold_available.to_dict()))
        if self.market_type == MARKET_TYPE.FUTURE_CN:

            self.static_balance['frozen'].append(
                sum([
                    rx['money'] * rx['amount'] for var in self.frozen.values()
                    for rx in var.values()
                ]))

            self.static_balance['cash'].append(self.cash[-1])
            self.static_balance['hold'].append(self.hold.to_dict())
            self.static_balance['date'].append(self.date)
            """静态权益的结算

            只关心开仓价/ 不做盯市制度

            动态权益的结算需要关心

            """

            self.static_balance['static_assets'].append(
                self.static_balance['cash'][-1] +
                self.static_balance['frozen'][-1])

        self.sell_available = self.hold
        self.buy_available = self.hold
        self.datetime = '{} 09:30:00'.format(QA_util_get_next_day(
            self.date)) if self.date is not None else None

    def on_bar(self, event):
        '''
        策略事件
        :param event:
        :return:
        '''
        'while updating the market data'

        print("on_bar account {} ".format(self.account_cookie),
              event.market_data)

    def on_tick(self, event):
        '''
        策略事件
        :param event:
        :return:
        '''
        'on tick event'
        print("on_tick ", event.market_data)
        pass

    def from_message(self, message):
        """resume the account from standard message
        这个是从数据库恢复账户时需要的"""
        self.account_cookie = message.get('account_cookie', None)
        self.portfolio_cookie = message.get('portfolio_cookie', None)
        self.user_cookie = message.get('user_cookie', None)
        self.broker = message.get('broker', None)
        self.market_type = message.get('market_type', None)
        self.strategy_name = message.get('strategy_name', None)
        self._currenttime = message.get('current_time', None)
        self.allow_sellopen = message.get('allow_sellopen', False)
        self.allow_t0 = message.get('allow_t0', False)
        self.margin_level = message.get('margin_level', False)
        self.init_cash = message.get('init_cash',
                                     message.get('init_assets',
                                                 1000000))  # 兼容修改
        self.init_hold = pd.Series(message.get('init_hold', {}), name='amount')
        self.init_hold.index.name = 'code'
        self.commission_coeff = message.get('commission_coeff', 0.00015)
        self.tax_coeff = message.get('tax_coeff', 0.0015)
        self.history = message['history']
        self.cash = message['cash']
        self.time_index = message['trade_index']
        self.running_time = message.get('running_time', None)
        self.quantaxis_version = message.get('quantaxis_version', None)
        self.running_environment = message.get('running_environment',
                                               RUNNING_ENVIRONMENT.BACKETEST)
        self.settle()
        return self

    @property
    def table(self):
        """
        打印出account的内容
        """
        return pd.DataFrame([
            self.message,
        ]).set_index('account_cookie', drop=False).T

    def run(self, event):
        '''
        这个方法是被 QA_ThreadEngine 处理队列时候调用的, QA_Task 中 do 方法调用 run (在其它线程中)
       'QA_WORKER method 重载'
        :param event: 事件类型 QA_Event
        :return:
        '''
        'QA_WORKER method'
        if event.event_type is ACCOUNT_EVENT.SETTLE:
            self.settle()

        # elif event.event_type is ACCOUNT_EVENT.UPDATE:
        #     self.receive_deal(event.message)
        elif event.event_type is ACCOUNT_EVENT.MAKE_ORDER:
            """generate order
            if callback callback the order
            if not return back the order
            """
            data = self.send_order(code=event.code,
                                   amount=event.amount,
                                   time=event.time,
                                   amount_model=event.amount_model,
                                   towards=event.towards,
                                   price=event.price,
                                   order_model=event.order_model)
            if event.callback:
                event.callback(data)
            else:
                return data
        elif event.event_type is ENGINE_EVENT.UPCOMING_DATA:
            """update the market_data
            1. update the inside market_data struct
            2. tell the on_bar methods

            # 这样有点慢


            """

            self._currenttime = event.market_data.datetime[0]
            if self._market_data is None:
                self._market_data = event.market_data
            else:
                self._market_data = self._market_data + event.market_data
            self.on_bar(event)

            if event.callback:
                event.callback(event)

    def save(self):
        """
        存储账户信息
        """
        save_account(self.message)

    def sync_account(self, sync_message):
        """同步账户

        Arguments:
            sync_message {[type]} -- [description]
        """

        self.init_hold = sync_message['hold_available']
        self.init_cash = sync_message['cash_available']

        self.sell_available = copy.deepcopy(self.init_hold)
        self.history = []
        self.cash = [self.init_cash]
        self.cash_available = self.cash[-1]  # 在途资金

    def change_cash(self, money):
        """
        外部操作|高危|
        """
        res = self.cash[-1] + money
        if res >= 0:
            # 高危操作
            self.cash[-1] = res

    def get_orders(self, if_today=True):
        '''
        返回当日委托/历史委托
        :param if_today: true 只返回今天的订单
        :return: QA_OrderQueue
        '''
        # 🛠todo 筛选其它不是今天的订单返回
        return self.orders

    def get_history(self, start, end):
        """返回历史成交

        Arguments:
            start {str} -- [description]
            end {str]} -- [description]
        """
        return self.history_table.set_index('datetime', drop=False).loc[slice(
            pd.Timestamp(start), pd.Timestamp(end))]
示例#4
0
class QA_Performance():
    """
    QA_Performance是一个绩效分析插件

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


    QAPERFORMANCE 的评估字段

    1. 对于多头开仓/ 空头开仓的分析
    2. 总盈利(对于每个单笔而言)
    3. 总亏损(对于每个单笔而言)
    4. 总盈利/总亏损
    5. 交易手数
    6. 盈利比例
    7. 盈利手数
    8. 亏损手数
    9. 持平手数
    10. 平均利润
    11. 平均盈利
    12. 平均亏损
    13. 平均盈利/平均亏损
    14. 最大盈利(单笔)
    15. 最大亏损(单笔)
    16. 最大盈利/总盈利
    17. 最大亏损/总亏损
    18. 净利润/最大亏损
    19. 最大连续盈利手数
    20. 最大连续亏损手数
    21. 平均持仓周期
    22. 平均盈利周期
    23. 平均亏损周期
    24. 平均持平周期
    25. 最大使用资金
    26. 最大持仓手数
    27. 交易成本合计
    28. 收益率
    29. 年化收益率
    30. 有效收益率
    31. 月度平均盈利
    32. 收益曲线斜率
    33. 收益曲线截距
    34. 收益曲线R2值
    35. 夏普比例
    36. 总交易时间
    37. 总持仓时间
    38. 持仓时间比例
    39. 最大空仓时间
    40. 持仓周期
    41. 资产最大升水
    42. 发生时间
    43. 最大升水/前期低点
    44. 单日最大资产回撤比率
    45. 最大资产回撤值
    46. 最大资产回撤发生时间
    47. 回撤值/前期高点
    48. 净利润/回撤值


    """

    def __init__(self, target):

        self.target = target
        self._style_title = [
            'beta',
            'momentum',
            'size',
            'earning_yield',
            'volatility',
            'growth',
            'value',
            'leverage',
            'liquidity',
            'reversal'
        ]
        self.market_preset = MARKET_PRESET()
        self.pnl = self.pnl_fifo

    def __repr__(self):
        return '< QA_PERFORMANCE ANYLYSIS PLUGIN >'

    def set_pnl(self, model='fifo'):
        if model == 'fifo':
            self.pnl = self.pnl_fifo
        elif model == 'lifo':
            self.pnl = self.pnl_lifo

    def base_message(self, pnl):
        return {'total_profit': round(self.total_profit(pnl), 2),  # 总盈利(对于每个单笔而言)
                'total_loss': round(self.total_loss(pnl), 2),  # 总亏损(对于每个单笔而言)
                'total_pnl': round(self.total_pnl(pnl), 2),  # 总盈利/总亏损
                'trading_amounts': round(self.trading_amounts(pnl), 2),  # 交易手数
                'profit_amounts': round(self.profit_amounts(pnl), 2),  # 盈利手数
                'loss_amounts': round(self.loss_amounts(pnl), 2),  # 亏损手数
                'even_amounts': round(self.even_amounts(pnl), 2),  # 持平手数
                'profit_precentage': round(self.profit_precentage(pnl), 2),
                'loss_precentage': round(self.loss_precentage(pnl), 2),
                'even_precentage': round(self.even_precentage(pnl), 2),
                'average_profit': round(self.average_profit(pnl), 2),
                'average_loss': round(self.average_loss(pnl), 2),
                'average_pnl': round(self.average_pnl(pnl), 2),
                'max_profit': round(self.max_profit(pnl), 2),
                'max_loss': round(self.max_loss(pnl), 2),
                'max_pnl': round(self.max_pnl(pnl), 2),
                'netprofio_maxloss_ratio': round(self.netprofio_maxloss_ratio(pnl), 2),
                'continue_profit_amount': round(self.continue_profit_amount(pnl), 2),
                'continue_loss_amount': round(self.continue_loss_amount(pnl), 2),
                'average_holdgap': self.average_holdgap(pnl),
                'average_profitholdgap': self.average_profitholdgap(pnl),
                'average_losssholdgap': self.average_losssholdgap(pnl)}

    @property
    def message(self):
        """[summary]
            2. 
            3. 
            4. 
            5. 
            6. 
            7. 盈利手数
            8. 亏损手数
            9. 持平手数
            10. 平均利润
            11. 平均盈利
            12. 平均亏损
            13. 平均盈利/平均亏损
            14. 最大盈利(单笔)
            15. 最大亏损(单笔)
            16. 最大盈利/总盈利
            17. 最大亏损/总亏损
            18. 净利润/最大亏损
            19. 最大连续盈利手数
            20. 最大连续亏损手数
            21. 平均持仓周期
            22. 平均盈利周期
            23. 平均亏损周期
            24. 平均持平周期
            25. 最大使用资金
            26. 最大持仓手数
            27. 交易成本合计
            28. 收益率
            29. 年化收益率
            30. 有效收益率
            31. 月度平均盈利
            32. 收益曲线斜率
            33. 收益曲线截距
            34. 收益曲线R2值
            35. 夏普比例
            36. 总交易时间
            37. 总持仓时间
            38. 持仓时间比例
            39. 最大空仓时间
            40. 持仓周期
            41. 资产最大升水
            42. 发生时间
            43. 最大升水/前期低点
            44. 单日最大资产回撤比率
            45. 最大资产回撤值
            46. 最大资产回撤发生时间
            47. 回撤值/前期高点
            48. 净利润/回撤值
        Returns:
            [type] -- [description]
        """

        return {
            # 总盈利(对于每个单笔而言)
            'total_profit': round(self.total_profit(self.pnl), 2),
            'total_loss': round(self.total_loss(self.pnl), 2),  # 总亏损(对于每个单笔而言)
            'total_pnl': round(self.total_pnl(self.pnl), 2),  # 总盈利/总亏损
            # 交易手数
            'trading_amounts': round(self.trading_amounts(self.pnl), 2),
            'profit_amounts': round(self.profit_amounts(self.pnl), 2),  # 盈利手数
            'loss_amounts': round(self.loss_amounts(self.pnl), 2),  # 亏损手数
            'even_amounts': round(self.even_amounts(self.pnl), 2),  # 持平手数
            'profit_precentage': round(self.profit_precentage(self.pnl), 2),
            'loss_precentage': round(self.loss_precentage(self.pnl), 2),
            'even_precentage': round(self.even_precentage(self.pnl), 2),
            'average_profit': round(self.average_profit(self.pnl), 2),
            'average_loss': round(self.average_loss(self.pnl), 2),
            'average_pnl': round(self.average_pnl(self.pnl), 2),
            'max_profit': round(self.max_profit(self.pnl), 2),
            'max_loss': round(self.max_loss(self.pnl), 2),
            'max_pnl': round(self.max_pnl(self.pnl), 2),
            'netprofio_maxloss_ratio': round(self.netprofio_maxloss_ratio(self.pnl), 2),
            'continue_profit_amount': round(self.continue_profit_amount(self.pnl), 2),
            'continue_loss_amount': round(self.continue_loss_amount(self.pnl), 2),
            'average_holdgap': self.average_holdgap(self.pnl),
            'average_profitholdgap': self.average_profitholdgap(self.pnl),
            'average_losssholdgap': self.average_losssholdgap(self.pnl),
            'buyopen': self.base_message(self.pnl_buyopen),
            'sellopen': self.base_message(self.pnl_sellopen)
        }

    @property
    def prefer(self):
        pass

    @property
    def style(self):
        """风格分析
        """
        pass

    @property
    def pnl_lifo(self):
        """
        使用后进先出法配对成交记录
        """
        X = dict(
            zip(
                self.target.code,
                [LifoQueue() for i in range(len(self.target.code))]
            )
        )
        pair_table = []
        for _, data in self.target.history_table_min.iterrows():
            while True:
                if X[data.code].qsize() == 0:
                    X[data.code].put((data.datetime, data.amount, data.price))
                    break
                else:
                    l = X[data.code].get()
                    if (l[1] * data.amount) < 0:
                        # 原有多仓/ 平仓 或者原有空仓/平仓

                        if abs(l[1]) > abs(data.amount):
                            temp = (l[0], l[1] + data.amount, l[2])
                            X[data.code].put_nowait(temp)
                            if data.amount < 0:
                                pair_table.append(
                                    [
                                        data.code,
                                        data.datetime,
                                        l[0],
                                        abs(data.amount),
                                        data.price,
                                        l[2]
                                    ]
                                )
                                break
                            else:
                                pair_table.append(
                                    [
                                        data.code,
                                        l[0],
                                        data.datetime,
                                        abs(data.amount),
                                        l[2],
                                        data.price

                                    ]
                                )
                                break

                        elif abs(l[1]) < abs(data.amount):
                            data.amount = data.amount + l[1]

                            if data.amount < 0:
                                pair_table.append(
                                    [
                                        data.code,
                                        data.datetime,
                                        l[0],
                                        l[1],
                                        data.price,
                                        l[2]
                                    ]
                                )
                            else:
                                pair_table.append(
                                    [
                                        data.code,
                                        l[0],
                                        data.datetime,
                                        l[1],
                                        l[2],
                                        data.price
                                    ]
                                )
                        else:
                            if data.amount < 0:
                                pair_table.append(
                                    [
                                        data.code,
                                        data.datetime,
                                        l[0],
                                        abs(data.amount),
                                        data.price,
                                        l[2]
                                    ]
                                )
                                break
                            else:
                                pair_table.append(
                                    [
                                        data.code,
                                        l[0],
                                        data.datetime,
                                        abs(data.amount),
                                        l[2],
                                        data.price
                                    ]
                                )
                                break

                    else:
                        X[data.code].put_nowait(l)
                        X[data.code].put_nowait(
                            (data.datetime,
                             data.amount,
                             data.price)
                        )
                        break

        pair_title = [
            'code',
            'sell_date',
            'buy_date',
            'amount',
            'sell_price',
            'buy_price'
        ]
        pnl = pd.DataFrame(pair_table, columns=pair_title).set_index('code')
        pnl = pnl.assign(
            unit=pnl.code.apply(lambda x: self.market_preset.get_unit(x)),
            pnl_ratio=(pnl.sell_price / pnl.buy_price) - 1,
            sell_date=pd.to_datetime(pnl.sell_date),
            buy_date=pd.to_datetime(pnl.buy_date)
        )
        pnl = pnl.assign(
            pnl_money=(pnl.sell_price - pnl.buy_price) * pnl.amount * pnl.unit,
            hold_gap=abs(pnl.sell_date - pnl.buy_date),
            if_buyopen=(pnl.sell_date -
                        pnl.buy_date) > datetime.timedelta(days=0)
        )
        pnl = pnl.assign(
            openprice=pnl.if_buyopen.apply(
                lambda pnl: 1 if pnl else 0) * pnl.buy_price + pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_price,
            opendate=pnl.if_buyopen.apply(
                lambda pnl: 1 if pnl else 0) * pnl.buy_date.map(str) + pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_date.map(str),
            closeprice=pnl.if_buyopen.apply(
                lambda pnl: 0 if pnl else 1) * pnl.buy_price + pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_price,
            closedate=pnl.if_buyopen.apply(
                lambda pnl: 0 if pnl else 1) * pnl.buy_date.map(str) + pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_date.map(str))
        return pnl.set_index('code')

    @property
    def pnl_buyopen(self):
        return self.pnl[self.pnl.if_buyopen]

    @property
    def pnl_sellopen(self):
        return self.pnl[~self.pnl.if_buyopen]

    @property
    def pnl_fifo(self):
        X = dict(
            zip(
                self.target.code,
                [deque() for i in range(len(self.target.code))]
            )
        )
        pair_table = []
        for _, data in self.target.history_table_min.iterrows():
            while True:
                if len(X[data.code]) == 0:
                    X[data.code].append(
                        (data.datetime,
                         data.amount,
                         data.price)
                    )
                    break
                else:
                    l = X[data.code].popleft()
                    if (l[1] * data.amount) < 0:
                        # 原有多仓/ 平仓 或者原有空仓/平仓

                        if abs(l[1]) > abs(data.amount):
                            temp = (l[0], l[1] + data.amount, l[2])
                            X[data.code].appendleft(temp)
                            if data.amount < 0:
                                pair_table.append(
                                    [
                                        data.code,
                                        data.datetime,
                                        l[0],
                                        abs(data.amount),
                                        data.price,
                                        l[2]
                                    ]
                                )
                                break
                            else:
                                pair_table.append(
                                    [
                                        data.code,
                                        l[0],
                                        data.datetime,
                                        abs(data.amount),
                                        l[2],
                                        data.price

                                    ]
                                )
                                break

                        elif abs(l[1]) < abs(data.amount):
                            data.amount = data.amount + l[1]

                            if data.amount < 0:
                                pair_table.append(
                                    [
                                        data.code,
                                        data.datetime,
                                        l[0],
                                        l[1],
                                        data.price,
                                        l[2]
                                    ]
                                )
                            else:
                                pair_table.append(
                                    [
                                        data.code,
                                        l[0],
                                        data.datetime,
                                        l[1],
                                        l[2],
                                        data.price

                                    ]
                                )
                        else:
                            if data.amount < 0:
                                pair_table.append(
                                    [
                                        data.code,
                                        data.datetime,
                                        l[0],
                                        abs(data.amount),
                                        data.price,
                                        l[2]
                                    ]
                                )
                                break
                            else:
                                pair_table.append(
                                    [
                                        data.code,
                                        l[0],
                                        data.datetime,
                                        abs(data.amount),
                                        l[2],
                                        data.price


                                    ]
                                )
                                break

                    else:
                        X[data.code].appendleft(l)
                        X[data.code].appendleft(
                            (data.datetime,
                             data.amount,
                             data.price)
                        )
                        break

        pair_title = [
            'code',
            'sell_date',
            'buy_date',
            'amount',
            'sell_price',
            'buy_price'
        ]
        pnl = pd.DataFrame(pair_table, columns=pair_title)

        pnl = pnl.assign(
            unit=pnl.code.apply(lambda x: self.market_preset.get_unit(x)),
            pnl_ratio=(pnl.sell_price / pnl.buy_price) - 1,
            sell_date=pd.to_datetime(pnl.sell_date),
            buy_date=pd.to_datetime(pnl.buy_date)
        )
        pnl = pnl.assign(
            pnl_money=(pnl.sell_price - pnl.buy_price) * pnl.amount * pnl.unit,
            hold_gap=abs(pnl.sell_date - pnl.buy_date),
            if_buyopen=(pnl.sell_date -
                        pnl.buy_date) > datetime.timedelta(days=0)
        )
        pnl = pnl.assign(
            openprice=pnl.if_buyopen.apply(
                lambda pnl: 1 if pnl else 0) * pnl.buy_price + pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_price,
            opendate=pnl.if_buyopen.apply(
                lambda pnl: 1 if pnl else 0) * pnl.buy_date.map(str) + pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_date.map(str),
            closeprice=pnl.if_buyopen.apply(
                lambda pnl: 0 if pnl else 1) * pnl.buy_price + pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_price,
            closedate=pnl.if_buyopen.apply(
                lambda pnl: 0 if pnl else 1) * pnl.buy_date.map(str) + pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_date.map(str))
        return pnl.set_index('code')

    def plot_pnlratio(self):
        """
        画出pnl比率散点图
        """

        plt.scatter(x=self.pnl.sell_date.apply(str), y=self.pnl.pnl_ratio)
        plt.gcf().autofmt_xdate()
        return plt

    def plot_pnlmoney(self):
        """
        画出pnl盈亏额散点图
        """
        plt.scatter(x=self.pnl.sell_date.apply(str), y=self.pnl.pnl_money)
        plt.gcf().autofmt_xdate()
        return plt

    def abnormal_active(self):
        """
        账户的成交发生异常成交记录的分析
        """
        pass

    def brinson(self):
        """Brinson Model analysis
        """
        pass

    def hold(self):
        """持仓分析
        """
        pass

    def win_rate(self):
        """胜率

        胜率
        盈利次数/总次数
        """
        data = self.pnl
        try:
            return round(len(data.query('pnl_money>0')) / len(data), 2)
        except ZeroDivisionError:
            return 0

    @property
    def accumulate_return(self):
        """
        returns a pd-Dataframe format accumulate return for different periods
        """
        pass

    def save(self):
        """save the performance analysis result to database
        """
        pass

    def profit_pnl(self, pnl):
        return pnl.query('pnl_money>0')

    def loss_pnl(self, pnl):
        return pnl.query('pnl_money<0')

    def even_pnl(self, pnl):
        return pnl.query('pnl_money==0')

    def total_profit(self, pnl):
        if len(self.profit_pnl(pnl))>0:
            return self.profit_pnl(pnl).pnl_money.sum()
        else:
            return 0

    def total_loss(self, pnl):
        if len(self.loss_pnl(pnl))>0:
            return self.loss_pnl(pnl).pnl_money.sum()
        else:
            return 0

    def total_pnl(self, pnl):
        try:
            return abs(self.total_profit(pnl) / self.total_loss(pnl))
        except ZeroDivisionError:
            return 0

    def trading_amounts(self, pnl):
        return len(pnl)

    def profit_amounts(self, pnl):
        return len(self.profit_pnl(pnl))

    def loss_amounts(self, pnl):
        return len(self.loss_pnl(pnl))

    def even_amounts(self, pnl):
        return len(self.even_pnl(pnl))

    def profit_precentage(self, pnl):
        try:
            return self.profit_amounts(pnl) / self.trading_amounts(pnl)
        except ZeroDivisionError:
            return 0

    def loss_precentage(self, pnl):
        try:
            return self.loss_amounts(pnl) / self.trading_amounts(pnl)
        except ZeroDivisionError:
            return 0

    def even_precentage(self, pnl):
        try:
            return self.even_amounts(pnl) / self.trading_amounts(pnl)
        except ZeroDivisionError:
            return 0

    def average_loss(self, pnl):
        if len(self.loss_pnl(pnl))>0:
            return self.loss_pnl(pnl).pnl_money.mean()
        else:
            return 0

    def average_profit(self, pnl):
        if len(self.profit_pnl(pnl))>0:
            return self.profit_pnl(pnl).pnl_money.mean()
        else:
            return 0

    def average_pnl(self, pnl):
        if len(self.loss_pnl(pnl))>0 and len(self.profit_pnl(pnl))>0:
            try:
                return abs(self.average_profit(pnl) / self.average_loss(pnl))
            except ZeroDivisionError:
                return 0
        else:
            return 0

    def max_profit(self, pnl):
        if len(self.profit_pnl(pnl))>0:
            return self.profit_pnl(pnl).pnl_money.max()
        else:
            return 0

    def max_loss(self, pnl):
        if len(self.loss_pnl(pnl))>0:
            return self.loss_pnl(pnl).pnl_money.min()
        else:
            return 0

    def max_pnl(self, pnl):
        try:
            return abs(self.max_profit(pnl) / self.max_loss(pnl))
        except ZeroDivisionError:
            return 0

    def netprofio_maxloss_ratio(self, pnl):
        if len(self.loss_pnl(pnl))>0:
            try:
                return abs(pnl.pnl_money.sum() / self.max_loss(pnl))
            except ZeroDivisionError:
                return 0
        else:
            return 0

    def continue_profit_amount(self, pnl):
        w = []
        w1 = 0
        for _, item in pnl.pnl_money.iteritems():
            if item > 0:
                w1 += 1
            elif item < 0:
                w.append(w1)
                w1 = 0
        if len(w) == 0:
            return 0
        else:
            return max(w)

    def continue_loss_amount(self, pnl):
        l = []
        l1 = 0
        for _, item in pnl.pnl_money.iteritems():
            if item > 0:
                l1 += 1
            elif item < 0:
                l.append(l1)
                l1 = 0
        if len(l) == 0:
            return 0
        else:
            return max(l)

    def average_holdgap(self, pnl):
        if len(pnl.hold_gap)>0:
            return str(pnl.hold_gap.mean())
        else:
            return 'no trade'

    def average_profitholdgap(self, pnl):
        if len(self.profit_pnl(pnl).hold_gap)>0:
            return str(self.profit_pnl(pnl).hold_gap.mean())
        else:
            return 'no trade'

    def average_losssholdgap(self, pnl):
        if len(self.loss_pnl(pnl).hold_gap)>0:
            return str(self.loss_pnl(pnl).hold_gap.mean())
        else:
            return 'no trade'

    def average_evenholdgap(self, pnl):
        if len(self.even_pnl(pnl).hold_gap)>0:
            return self.even_pnl(pnl).hold_gap.mean()
        else:
            return 'no trade'

    @property
    def max_cashused(self):
        return self.target.init_cash - min(self.target.cash)

    @property
    def total_taxfee(self):
        return self.target.history_table_min.commission.sum(
        ) + self.target.history_table_min.tax.sum()