예제 #1
0
    def check_db_data(cls,
                      code,
                      start_date,
                      end_date,
                      table,
                      db='server_db',
                      dbname='big-data',
                      db2='server_db',
                      dbname2='big-data',
                      path='f://zjf//check_data//'):
        if table == None: raise Exception("table can't be empty")
        data = KlineData(location=db,
                         dbname=dbname).read_data(code=code,
                                                  start_date=start_date,
                                                  end_date=end_date,
                                                  kline=table,
                                                  timemerge=True)
        data = data.sort_values(by=['date'], ascending=True)

        data2 = KlineData(location=db2,
                          dbname=dbname2).read_data(code=code,
                                                    start_date=start_date,
                                                    end_date=end_date,
                                                    kline=table,
                                                    timemerge=True)
        data2 = data2.sort_values(by=['date'], ascending=True)
        print('start draw.......')
        # fig, ax = plt.subplots()
        # fig.subplots_adjust(bottom=0.2)
        ax = plt.subplot(211)
        # plt.title('本地')
        ax.set_xticks([])
        candlestick2_ohlc(ax,
                          data.open,
                          data.high,
                          data.low,
                          data.close,
                          width=1,
                          colorup='red',
                          colordown='green')
        plt.title(db + '--' + table + '--' + code)
        ax = plt.subplot(212)
        # plt.title('远程')
        plt.title(db2 + '--' + table + '--' + code)
        candlestick2_ohlc(ax,
                          data2.open,
                          data2.high,
                          data2.low,
                          data2.close,
                          width=1,
                          colorup='red',
                          colordown='green')
        # plt.show()
        File.check_file(path=path)
        ax.set_xticks([])
        plt.savefig(path + table + '.png', transparent=True)
        plt.close()
예제 #2
0
class ModelValidator:
    """
    一个广泛适用的模型回测验证工具,
    适用于A股大部分的交易场景,支持多空对冲,
    对验证得到的交易清单进行量化评估,
    其他交易场景
    """
    __metaclass__ = ABCMeta
    model_name = ''
    kline = ''
    data = pd.DataFrame([])
    index_flag = False
    sd = datetime.datetime(2017, 1, 1)  # 默认回测的时间起点
    ed = datetime.datetime(2018, 1, 1)
    # 交易策略的相关参数
    model_param = {
        'INDEX_REFERENCE': '399303',
        'price': 10,  # 股票的单价(收盘)
        'amount': 20000000,  # 成交额
        'max_pst_vol': 10,  # 最大持仓量
        'stop_loss': 0.1,  # 止损点
        'stop_get': 0.1,  # 止赢点
        'fee': 0.005,  # 交易费用
        'max_pst_days': 1,  # 最大持仓天数
        'max_pst_mins': 0,  # 最大持有分钟数,最终的持有时间将是天数+分钟数
        'lever': 1,  # 杠杆率
    }
    # 日内支持验证的时间点(适用于A股),其他市场的日内回测需要重置这些变量
    min60 = [1030, 1130, 1400, 1500]
    min30 = [1000, 1030, 1100, 1130, 1330, 1400, 1430, 1500]
    min15 = [
        945, 1000, 1015, 1030, 1045, 1100, 1115, 1130, 1315, 1330, 1345, 1400,
        1415, 1430, 1445, 1500
    ]
    min5 = [
        935, 940, 945, 950, 955, 1000, 1005, 1010, 1015, 1020, 1025, 1030,
        1035, 1040, 1045, 1050, 1055, 1100, 1105, 1110, 1115, 1120, 1125, 1130,
        1305, 1310, 1315, 1320, 1325, 1330, 1335, 1340, 1345, 1350, 1355, 1400,
        1405, 1410, 1415, 1420, 1425, 1430, 1435, 1440, 1445, 1450, 1455, 1500
    ]

    # 验证得到的最终结果最少包含但不限于cols描述的一个df
    cols = [
        'code', 'open_date', 'close_date', 'open_price', 'close_price',
        'real_profit', 'profit', 'type', 'reason', 'confidence'
    ]
    # 核心buy函数所必须的dict字段
    clo = ['code', 'open_date', 'open_price', 'type', 'confidence']
    ugly = list()  # 自定义的非交易时间,在这些时间点上将不会发生交易,通常是为了回避风险
    ntf = False  # 计算交易日的方法
    kline_data_location = None  # 回测过程中需要到那个位置读取k线数据
    kline_data_dbname = None
    kd = KlineData(kline_data_location, kline_data_dbname)

    @classmethod
    def VerifyFrame(cls,
                    data,
                    kline,
                    func=None,
                    start_date=None,
                    end_date=None,
                    ugly=list(),
                    ntf=False):
        """
        :param ntf: 按自然日[False]计算持有时间或交易日[True]持有时间
        :param ugly: 用户自定义的非交易时间,datetime类型的数组
        :param func:能够读取用于验证该模型的K线数据的函数
        :param data: Need to verify the signals
        :param kline: kline type of those signals
        :param start_date: The starting time for verifying the models's return
        :param end_date: The ending time for verifying the models's return
        :return:
        """
        try:
            cls.kline = kline
            cls.data = data
            if start_date is not None:
                cls.sd = start_date
            if end_date is not None:
                cls.ed = end_date
            cls.func = func if func is not None else cls.kd.read_data
            cls.ugly = ugly
            cls.ntf = ntf
            must = ['code', 'type', 'open_price', 'open_date', 'confidence']
            if set(must) <= set(data.columns):
                pass
            else:
                raise Exception
        except Exception:
            raise VerifyError('initialize this class raise error')
        pass

    @classmethod
    def modelparammodify(cls, **kw):
        """
        修改基本参数的值
        :param kw:
        :return:
        """
        try:
            keys = cls.model_param.keys()
            for k, v in zip(kw.keys(), kw.values()):
                if k in keys:
                    cls.model_param[k] = v
                else:
                    message = 'no find this key=%s in models param' % k
                    raise warning(message)
        except Exception as e:
            ExceptionInfo(e)

    @classmethod
    def reference(cls, date):
        """
        :param date: aims'date
        :return: true or false
        """
        try:
            if date in cls.ugly:
                return False
            else:
                return True
        except Exception as e:
            print(e)
            return False

    @classmethod
    def close(cls, data, mark, op):
        """
        默认的执行平仓策略的函数,这个平仓策略是指按止赢&止损&最长持有时间这种规则来
        执行平仓操作。
        :param data: 用于检验卖出信息的K线数据,是买入时间点之后的、合理的、可交易
        的、最晚的,通过data就可以计算出在什么时间点卖出
        :param mark: 持仓类型
        :param op: 买价
        :return: 返回卖出的时间, 收益, 原因(-1表示止损, 1表示止盈, 0表示时间到)
        """
        sell_day = None
        _percent = 1
        if mark:  # 做空
            stop_get = cls.model_param['stop_get']
            stop_loss = cls.model_param['stop_loss']
            data = data[::-1]
            for i, r in data.iterrows():
                get_percent = r.low / op
                loss_percent = r.high / op
                sell_day = r.date
                if loss_percent >= 1 + stop_loss:  # 损失超过上限
                    return sell_day, loss_percent, -1, r.high
                elif get_percent < 1 - stop_get:  # 达到预期收益
                    return sell_day, get_percent, 1, r.low
                else:
                    _percent = r.close / op
                    close_price = r.close
        else:  # 做多
            stop_get = cls.model_param['stop_get']
            stop_loss = cls.model_param['stop_loss']
            data = data[::-1]
            for i, r in data.iterrows():
                get_percent = r.high / op
                loss_percent = r.low / op
                sell_day = r.date
                if loss_percent <= 1 - stop_loss:
                    return sell_day, loss_percent, -1, r.low
                elif get_percent > 1 + stop_get:
                    return sell_day, get_percent, 1, r.high
                else:
                    _percent = r.close / op
                    close_price = r.close
        return sell_day, _percent, 0, close_price  # 未能止损盈

    @classmethod
    def simple_close(cls, signal):
        """
        平仓的信息直接由回测的历史信号给出,这样我们在执行历史信号平仓函数的时候可以直接在
        历史信号中找到那个关于平仓的信息。当你想要执行这种类型的回测时,所给出的signal则
        必须包含额外的close_date,close_price这两个字段
        :param signal:
        :return:
        """
        try:
            # code = signal['code']
            close_date = signal['close_date']
            close_price = signal['close_price']
            open_price = signal['open_price']
            pf = close_price / open_price
            # d = cls.kd.read_one(code, close_date, cls.kline)
            # if close_price < d['close']:
            #     # 以卖出指导价卖出
            #     pf = close_price / open_price
            #     return close_date, pf, 0
            # else:
            #     pf = d['close'] / open_price
            #     return close_date, pf, 0
            return close_date, pf, 0, close_date
            pass
        except Exception as e:
            ExceptionInfo(e)

    @classmethod
    def usa_order_close(cls, signal):
        """
        适用于美股融资融券当日冲销账户(要成为这一类用户,账户余额必须大于$25,000),
        执行T+0交易规则,不受交割制度困扰(T+3),但可能会受到资金规模困扰,这与券商相关,
        外汇也适用于这种类型的平仓
        :param signal:
        :return:
        """
        try:
            code = signal['code']
            od = signal['open_date']
            open_price = signal['open_price']
            mark = signal['type']
            s = od
            for d in range(0, 12, 1):
                if cls.ntf:
                    # 持有时间按交易日计算
                    # 可能存在不合理的情况,因为目前我们不确定美股市场休市的安排
                    e = td.trade_period(od,
                                        days=d +
                                        cls.model_param['max_pst_days'],
                                        holidays=[])
                else:
                    e = od + datetime.timedelta(
                        days=d + cls.model_param['max_pst_days'])  # 持有时间按自然日计算
                e = e + datetime.timedelta(
                    minutes=cls.model_param['max_pst_mins'])
                try:
                    data = cls.func(code, s, e, cls.kline)
                except Exception:
                    raise TypeError('User-defined function raise a Exception')
                if len(data):
                    data = data[(data.date >= s) & (data.date <= e)]
                    if len(data):
                        return cls.close(data, mark, open_price)
            return e, 1, 404, 0
        except Exception as ep:
            ExceptionInfo(ep)

    @classmethod
    def default_order_close(cls, signal):
        """
        默认的一个平仓函数,它完成的任务就是接受开仓信号&用于验证这个信号的“未来的”、“历史的”
        数据,并将其传递给执行平仓动作的函数或直接在这里实现。总之,这个函数必要返回验证的结果。
        :return:
        """
        # 主要适用于中国A股(T+1),或与之交易规则基本一致的市场,其他市场可参考
        # 此函数描述了关于如何卖出资产的处理方式
        # 由于交易日不是连续的,当天买入后需第二交易日及以后才可以卖出
        # 这里最长等待12个休市日,如还未找到交易数据则本次交易收益计算无效
        # 为了确保做多与做空保持相同的风控策略,止盈止损系数对换
        # 这里的自然日不包括连续休市天数大于最长持有天数的那些天
        try:
            code = signal['code']
            date = signal['open_date']
            open_price = signal['open_price']
            mark = signal['type']
            od = datetime.datetime(date.year, date.month, date.day)
            s = od + datetime.timedelta(days=1)  # 买入时间的第二天
            for d in range(0, 12, 1):
                if cls.ntf:
                    e = td.trade_period(
                        od,
                        days=d + cls.model_param['max_pst_days'])  # 持有时间按交易日计算
                else:
                    e = od + datetime.timedelta(
                        days=d + cls.model_param['max_pst_days'])  # 持有时间按自然日计算
                e = e + datetime.timedelta(
                    minutes=cls.model_param['max_pst_mins'])
                try:
                    data = cls.func(code, s, e, cls.kline)
                except Exception:
                    raise TypeError('User-defined function raise a Exception')
                if len(data):
                    return cls.close(data, mark, open_price)
            return e, 1, 404, 0
        except Exception as ep:
            print(ep)
            return date, 1, 500, 0

    @classmethod
    def optimal_choice(cls, date, n):
        """
        When more than one stock at the same time, We need to go
        according to a custom method to choose some stocks
        :param date: There is more than one stock time
        :param n: The number of choices
        :return: Selected stock
        """
        try:
            selected_signals = cls.data[cls.data['open_date'] == date]
            # pst = stocks[stocks.c_mark == False]
            # ngt = stocks[stocks.c_mark == True]
            # ngt_ = len(ngt)
            # pst_ = len(pst)
            # t_ = n // 2
            # if n <= (ngt_ + pst_):  # 总数够
            #     if ngt_ < t_:   # 做空不够
            #         gt_ = ngt_
            #         st_ = n - gt_
            #     elif pst_ < t_:
            #         st_ = pst_
            #         gt_ = n - st_
            #     else:
            #         gt_ = t_
            #         st_ = n - gt_
            #     gt = ngt.sort_values(['confidence'], ascending=False).head(n=gt_)
            #     st = pst.sort_values(['confidence'], ascending=False).head(n=st_)
            #     return pd.concat([gt, st], axis=0, join='outer', ignore_index=True)
            # else:
            # 倒序
            return selected_signals.sort_values(['confidence'],
                                                ascending=True).head(n=n)
        except Exception as e:
            print(e)
            return pd.DataFrame([])

    @classmethod
    def order_open(cls, signal, order_close=None):
        """
        开仓引擎
        根据signal提供的品种代码、建议买入时间点、建议买入价格、建仓类型,
        按order_close函数约定的交易规则执行交易,order_close会返回本次模拟交易
        的结果,它们分别是出售的时间,实际纯收益,出售原因。相关交易费用也将
        会在这里处理
        :param order_close: 用户自定义的平仓函数
        :param signal: need to buy stock
        :return: the result of this buy
        """
        c = signal['code']
        open_date = signal['open_date']
        if cls.reference(open_date):
            open_price = signal['open_price']
            mark = signal['type']
            if order_close is None:
                sell_date, profit_percent, r, close_price = cls.default_order_close(
                    signal)
            else:
                try:
                    sell_date, profit_percent, r, close_price = order_close(
                        signal)
                except Exception:
                    raise TypeError('User-defined function raise a Exception')
            # print(sell_day, profit_percent)
            profit = profit_percent
            stop_loss = cls.model_param['stop_loss']  # 0.02
            stop_get = cls.model_param['stop_get']
            if mark:  # 做空
                if profit > 1 + stop_loss:  # 亏损
                    profit = 1 + cls.model_param['fee'] + stop_loss
                elif profit < 1 - stop_get:
                    profit = 1 + cls.model_param['fee'] - stop_get
                else:
                    profit = profit_percent + cls.model_param['fee']  # 计算交易成本
                # sell_price = open_price * profit
                profit = profit * -1 + 1  # 做空的特殊处理
                profit_percent = profit_percent * -1 + 1
            else:
                if profit > 1 + stop_get:
                    profit = 1 - cls.model_param['fee'] + stop_get
                elif profit < 1 - stop_loss:
                    profit = 1 - cls.model_param['fee'] - stop_loss
                else:
                    profit = profit_percent - cls.model_param['fee']  # 计算交易成本
                # sell_price = open_price * profit
                profit -= 1
                profit_percent -= 1
            profit *= cls.model_param['lever']
            # profit_percent实际收益
            rlt = dict(code=c,
                       open_date=open_date,
                       open_price=open_price,
                       close_date=sell_date,
                       close_price=close_price,
                       profit=profit,
                       real_profit=profit_percent,
                       type=mark,
                       reason=r,
                       confidence=signal['confidence'])
            return rlt

        else:
            return dict(code=c,
                        open_date=open_date,
                        open_price=signal['open_price'],
                        type=signal['type'],
                        reason=500,
                        confidence=signal['confidence'])

    @classmethod
    def verify_day(cls, optimal_choice=None, order_close=None):
        """
        日级别的回测验证
        :param optimal_choice: 同一时间点出现多个信号是,按照什么样的策略选择,需要
        通过这个函数来实现,默认按本验证器提供的函数选择
        :param order_close: 对于不同的市场而言卖出的规则可能不一样,这就需要按时间
        情况重写,默认按本验证器提供的函数实现(适用于A股市场)
        :return: models's win rate, Simple interest and Compound interest
        """
        test_days = (cls.ed - cls.sd).days
        good = pd.DataFrame([], columns=cls.cols)
        goods = pd.DataFrame([], columns=cls.cols)
        Rr = 1
        select_func = cls.optimal_choice if optimal_choice is None else optimal_choice
        order_close = cls.default_order_close if order_close is None else order_close
        sRr = 1 / cls.model_param['max_pst_vol']  # 一个标准单所占的资金比例
        for i in range(test_days):
            e = cls.sd + datetime.timedelta(days=i)  # 日期指针
            pst_ = len(good)  # 当前持仓量
            need_count = cls.model_param['max_pst_vol'] - pst_  # 剩余仓位
            # print(e, 'need_count:', need_count, list(good.stock_code))
            br = Rr / need_count if need_count != 0 else 0
            br = sRr if br > sRr else br
            if need_count > 0 and Rr > 0:
                # 根据需要购买的数量选择购买的股票
                buys = select_func(e, int(need_count))
                g = pd.DataFrame([
                    cls.order_open(dict(buys.loc[i]), order_close)
                    for i in buys.index
                ],
                                 columns=cls.cols)
                # 购买过程中可能有购买失败的
                g.dropna(axis=0, inplace=True)
                bct_ = len(g)
                if bct_ > 0:
                    g['Rr'] = br
                    Rr -= br * bct_
                    goods = pd.concat([g, goods],
                                      axis=0,
                                      join='outer',
                                      ignore_index=True)
                    good = pd.concat([g, good],
                                     axis=0,
                                     join='outer',
                                     ignore_index=True)

            drop_rows = list(good[good['close_date'] == e].index)
            if len(drop_rows):
                s = good.loc[drop_rows]
                Rr += ((s.profit + 1) * s.Rr).sum()
                '''回笼资金后不补填缺口,仍以不高于原始资金量的比例入市'''
                Rr = 1 if Rr > 1 else Rr
                good.drop(drop_rows, axis=0, inplace=True)
            # progress_bar(total=test_days, complete=i)
            print('\r{0}/{1}'.format(i + 1, test_days), end=' ', flush=True)
            pass
        if len(goods):
            menu = goods.copy(deep=True)
            tcs = len(goods)
            menu['MC'] = menu.profit * menu.Rr  # 边际贡献
            menu = menu.groupby(['close_date'],
                                as_index=False).agg({'MC': 'sum'})
            menu = menu.rename(columns={'MC': 'profit', 'close_date': 'date'})
            win_rate = (goods.profit > 0).sum() / tcs
            dcs = len(menu)  # 交易次数以日计
            avg_signals = len(goods) / 250.0
            profits = fi.already_get(menu.profit.tolist())
            max_dd = fi.max_drawdown(profits)
            spr = menu.profit.sum()
            cpr = (menu.profit.mean() + 1)**dcs - 1  # 复利,估计数
            apr = fi.annualized_returns(menu.profit.tolist())  # 年化收益
            sharp = fi.sharp(menu.profit.tolist())
            dit = dict(win_rate=win_rate,
                       tcs=tcs,
                       dcs=dcs,
                       avg_signals=avg_signals,
                       max_dd=max_dd,
                       spr=spr,
                       cpr=cpr,
                       apr=apr,
                       sharp=sharp,
                       max_pst_vol=cls.model_param['max_pst_vol'],
                       max_pst_date=cls.model_param['max_pst_days'],
                       stop_get=cls.model_param['stop_get'],
                       stop_loss=cls.model_param['stop_loss'],
                       lever=cls.model_param['lever'],
                       start_date=cls.sd,
                       end_date=cls.ed,
                       date=datetime.datetime.now())
            goods['stop_loss'] = cls.model_param['stop_loss']
            goods['stop_get'] = cls.model_param['stop_get']
            return dit, menu, goods
        else:
            return None, None, None

    @classmethod
    def verify_min(cls, optimal_choice=None, order_close=None):
        """
        日内分钟级别的回测验证
        :param optimal_choice:
        :param order_close:
        :return:
        """
        test_days = (cls.ed - cls.sd).days
        good = pd.DataFrame([], columns=cls.cols)
        goods = pd.DataFrame([], columns=cls.cols)
        select_func = cls.optimal_choice if optimal_choice is None else optimal_choice
        order_close = cls.default_order_close if order_close is None else order_close
        times = {
            'min5': cls.min5,
            'min15': cls.min15,
            'min30': cls.min30,
            'min60': cls.min60
        }
        time = times[re.search('min\d+', cls.kline, re.I).group()]
        Rr = 1
        sRr = 1 / cls.model_param['max_pst_vol']  # 标准单所占比例
        """年内"""
        for i in range(test_days):
            e = cls.sd + datetime.timedelta(days=i)
            need_count = cls.model_param['max_pst_vol'] - len(
                good)  # 当前持有的股票数量
            # print(e, 'position_count:', len(good))
            br = Rr / need_count if need_count != 0 else 0
            """日内"""
            for t in time:
                br = sRr if br > sRr else br
                bt = e + datetime.timedelta(hours=t // 100, minutes=t % 100)
                if need_count > 0 and Rr > 0:
                    # 根据需要购买的数量选择购买的股票
                    buys = select_func(bt, int(need_count))
                    # 购买
                    g = pd.DataFrame([
                        cls.order_open(dict(buys.loc[i]), order_close)
                        for i in buys.index
                    ],
                                     columns=cls.cols)
                    # 购买过程中可能有购买失败的
                    g.dropna(axis=0, inplace=True)
                    bct_ = len(g)  # 本次购买数量
                    if bct_ > 0:
                        """日内按最大持仓量计算固定投资比例,按实际资金池存量买入"""
                        # br = Rr / cls.model_param['max_pst_vol']
                        g['Rr'] = br
                        Rr -= br * bct_
                        goods = pd.concat([g, goods],
                                          axis=0,
                                          join='outer',
                                          ignore_index=True)
                        good = pd.concat([g, good],
                                         axis=0,
                                         join='outer',
                                         ignore_index=True)

                drop_rows = list(good[good['close_date'] <= bt].index)
                if len(drop_rows):
                    s = good.loc[drop_rows]
                    Rr += ((s.profit + 1) * s.Rr).sum()
                    '''回笼资金后不补填缺口,仍以不高于原始资金量的比例入市'''
                    Rr = 1 if Rr > 1 else Rr
                    good.drop(drop_rows, axis=0, inplace=True)
                need_count = cls.model_param['max_pst_vol'] - len(good)
                br = Rr / need_count if need_count != 0 else 0
                pass
            # """每一个交易日确认一次收益"""
            # db = goods[goods.open_date > e]  # e日新买入的股票
            # if len(db):
            #     # print(db)
            #     p = (db.profit * db.Rr).sum()
            #     rate_list.append(p)
            #     day_list.append(e)
            print('\r{0}/{1}'.format(i + 1, test_days), end=' ', flush=True)
        if len(goods):
            menu = goods.copy(deep=True)
            tcs = len(goods)
            menu['MC'] = menu.profit * menu.Rr  # 边际贡献
            menu['date'] = menu.close_date.dt.strftime('%Y-%m-%d')
            menu['date'] = pd.to_datetime(menu.date, format='%Y-%m-%d')
            menu = menu.groupby(['date'], as_index=False).agg({'MC': 'sum'})
            menu = menu.rename(columns={'MC': 'profit'})
            win_rate = (goods.profit > 0).sum() / len(goods)
            dcs = len(menu)  # 交易次数以日计
            avg_signals = tcs / 250.0
            profits = fi.already_get(menu.profit.tolist())
            max_dd = fi.max_drawdown(profits)
            spr = menu.profit.sum()
            cpr = (menu.profit.mean() + 1)**dcs - 1  # 复利,估计数
            apr = fi.annualized_returns(menu.profit.tolist())  # 年化收益
            sharp = fi.sharp(menu.profit.tolist())
            dit = dict(win_rate=win_rate,
                       tcs=tcs,
                       dcs=dcs,
                       avg_signals=avg_signals,
                       max_dd=max_dd,
                       spr=spr,
                       cpr=cpr,
                       apr=apr,
                       sharp=sharp,
                       max_pst_vol=cls.model_param['max_pst_vol'],
                       max_pst_date=cls.model_param['max_pst_days'],
                       stop_get=cls.model_param['stop_get'],
                       stop_loss=cls.model_param['stop_loss'],
                       lever=cls.model_param['lever'],
                       start_date=cls.sd,
                       end_date=cls.ed,
                       date=datetime.datetime.now())
            goods['stop_loss'] = cls.model_param['stop_loss']
            goods['stop_get'] = cls.model_param['stop_get']
            return dit, menu, goods
        else:
            return None, None, None

    @classmethod
    def param_optimization(cls, param_table, on='day', order_close=None):
        """
        暴力参数求解
        :param param_table:包含max_pst_vol,max_pst_days,stop_get,stop_loss交易参数组合
        的参数表,这是一个df
        :param on:何种级别的回测
        :return:
        """
        if on not in ('day', 'min'):
            raise ValueError('this "on" must in (day, min)')
        rls = list()
        for i, r in param_table.iterrows():
            try:
                # p = dict(max_pst_vol=r.max_pst_vol, max_pst_days=r.max_pst_days, stop_get=r.stop_get, stop_loss=r.stop_loss)
                cls.modelparammodify(max_pst_vol=r.max_pst_vol,
                                     max_pst_days=r.max_pst_days,
                                     stop_get=r.stop_get,
                                     stop_loss=r.stop_loss)
                dit, mus, tdr = cls.verify_day(
                    order_close) if on == 'day' else cls.verify_min(
                        order_close)
                rp = dict(dit, **r)
            except Exception as e:
                ExceptionInfo(e)
                rp = dict(r)
            finally:
                rls.append(rp)
        rls = pd.DataFrame(rls)
        return rls

    @classmethod
    def verify_all(cls, order_close=None):
        """
        不考虑持仓量,资金配比,单独看待某个信号在给定的平仓函数下的收益
        :param order_close: 平仓函数
        :return:
        """
        trades = list()
        order_close = cls.default_order_close if order_close is None else order_close
        counts = len(cls.data)
        n = 1
        for i, r in cls.data.iterrows():
            d = cls.order_open(dict(cls.data.loc[i, cls.clo]), order_close)
            d = dict(close_date=d['close_date'],
                     close_price=d['close_price'],
                     profit=d['profit'],
                     reason=d['reason'])
            trades.append(dict(d, **r))
            print('\r{0}/{1}'.format(n, counts), end=' ', flush=True)
            n += 1
        # 在输入的原始信号数据后面附加了回测的结果
        details = pd.DataFrame(trades)
        return details

    @classmethod
    def index_profit(cls, sd, ed, kline):
        """
        计算回测验证期内的大盘收益
        :param sd:
        :param ed:
        :param kline: 选择计算大盘收益的k线粒度
        :return: 返回以kline计的收益列表
        """
        index_data = cls.kd.read_data(code=cls.model_param['INDEX_REFERENCE'],
                                      start_date=sd,
                                      end_date=ed,
                                      kline=kline)
        index_data['last_close'] = index_data.close.shift(-1)
        index_data.dropna(inplace=True)
        index_data['profit'] = pd.eval(
            '(index_data.close - index_data.last_close) / index_data.last_close'
        )
        return index_data.loc[:, ['date', 'profit']]

    @classmethod
    def capm(cls, goods, kline):
        """
        计算Alpha, Beta这两个指标,由于这两个指标的计算比较复杂,
        所以在回测过程中不主动计算他们,当用户需要计算这两个指标
        时,把verify_XXX函数返回的goods传递给capm就可以计算得出
        Alpha, Beta.
        :param kline:
        :param goods:
        :return:
        """
        try:
            kline = 'index_' + re.search('(min\d+)|(day)', kline, re.I).group()
            index_data = cls.index_profit(sd=goods.open_date.min(),
                                          ed=goods.close_date.max(),
                                          kline=kline)
            index_profit = list()
            for i, r in goods.iterrows():
                idp = index_data[(index_data.date > r['open_date'])
                                 & (index_data.date <= r['close_date'])]
                index_profit.append(idp.profit.sum())
            goods['index_profit'] = index_profit
            menu = goods.copy(deep=True)
            menu['MC'] = menu.profit * menu.Rr  # 边际贡献
            if re.search('min', kline, re.I) is not None:
                menu['close_date'] = menu.close_date.dt.strftime('%Y-%m-%d')
                menu['close_date'] = pd.to_datetime(menu.close_date,
                                                    format='%Y-%m-%d')
            menu = menu.groupby(['close_date'], as_index=False).agg({
                'MC':
                'sum',
                'index_profit':
                'mean'
            })
            menu = menu.rename(columns={'MC': 'profit', 'close_date': 'date'})
            Alpha, Beta = fi.CAPM(menu.profit.tolist(),
                                  menu.index_profit.tolist())
            return Alpha, Beta, menu
        except Exception as e:
            ExceptionInfo(e)
            return 'N/A', 'N/A', None

    @classmethod
    def similar_amount(cls, goods, kline, gap=5):
        """
        计算交易信号发生后gap分钟内发生的交易额,适用于A股
        :param goods:
        :return:
        """
        try:
            goods['sa'] = np.nan
            goods['sl'] = np.nan
            goods['sh'] = np.nan
            for i, g in goods.iterrows():
                sd = datetime.datetime(g.open_date.year, g.open_date.month,
                                       g.open_date.day)
                if g.open_date.hour == 0 and g.open_date.minute == 0:
                    # 日线,特殊处理
                    tme = {'$in': [1455, 1500]}
                else:
                    # 日内
                    if g.open_date.hour == 15:
                        # 日内收盘时的信号
                        tme = 1500
                    elif g.open_date.hour == 11 and g.open_date.minute == 30:
                        # 日内上午收盘时的信号
                        tme = 1305
                    else:
                        tme = (g.open_date.minute + gap) // 5
                        dt = sd + datetime.timedelta(hours=g.open_date.hour,
                                                     minutes=tme * 5)
                        tme = dt.hour * 100 + dt.minute * 5
                d = cls.kd.read_data(code=g.code,
                                     start_date=sd,
                                     end_date=sd,
                                     kline=kline,
                                     time=tme)
                if len(d):
                    sa = d.amount.sum()
                    sl = d.low.min()
                    sh = d.high.max()
                else:
                    sa, sl, sh = np.nan, np.nan, np.nan

                goods.at[i, ['sa', 'sl', 'sh']] = sa, sl, sh
            goods['pv'] = pd.eval('(goods.sh - goods.sl) / goods.open_price')
            menu = goods.copy(deep=True)
            menu.dropna(axis=0, inplace=True)
            if len(menu):
                menu['date'] = menu.close_date.dt.strftime('%Y-%m-%d')
                menu['date'] = pd.to_datetime(menu.date, format='%Y-%m-%d')
                menu = menu.groupby(['date'], as_index=False).agg({
                    'sa': 'sum',
                    'pv': 'mean'
                })
            return goods, menu
            pass
        except Exception as e:
            ExceptionInfo(e)

    @classmethod
    def tick_amount(cls, goods, gap=5):
        try:
            from Calf import TickData as td
            goods['pa'] = np.nan
            goods['sa'] = np.nan
            goods['sl'] = np.nan
            goods['sh'] = np.nan
            for i, g in goods.iterrows():
                sd = datetime.datetime(g.open_date.year, g.open_date.month,
                                       g.open_date.day)
                if g.open_date.hour == 0 and g.open_date.minute == 0:
                    # 日线,特殊处理
                    tme = {'$gte': 1455, '$lte': 1500}
                    # tim = 0
                else:
                    # 日内
                    if g.open_date.hour == 15:
                        # 日内收盘时的信号
                        tme = {'$gte': 1455, '$lte': 1500}
                        # tim = 1500
                    elif g.open_date.hour == 11 and g.open_date.minute == 30:
                        # 日内上午收盘时的信号
                        tme = {'$gte': 1300, '$lte': 1300 + gap}
                        # tim = 1130
                    else:
                        dt = g.open_date + datetime.timedelta(minutes=gap)
                        stme = g.open_date.hour * 100 + g.open_date.minute
                        etme = dt.hour * 100 + dt.minute
                        tme = {'$gte': stme, '$lte': etme}
                        # tim = stme
                d = td.read_data(code=g.code,
                                 start_date=sd,
                                 end_date=sd,
                                 field={
                                     'date': True,
                                     'time': True,
                                     'price': True,
                                     'volume': True
                                 },
                                 time=tme,
                                 price={'$lte': g.open_price})
                if len(d):
                    sa = (d.price * d.volume * 100).sum()
                    sl = d.price.min()
                    sh = d.price.max()
                else:
                    sa, sl, sh = 0, 0, 0

                k = cls.kd.read_one_min(code=g.code,
                                        date=g.open_date,
                                        kline='kline_min5')
                if k is not None:
                    pa = k['amount']
                else:
                    pa = 0

                goods.at[i, ['pa', 'sa', 'sl', 'sh']] = pa, sa, sl, sh
            goods['pv'] = pd.eval('(goods.sh - goods.sl) / goods.open_price')
            menu = goods.copy(deep=True)
            if len(menu):
                menu['date'] = menu.open_date.dt.strftime('%Y-%m-%d')
                # menu['open_date'] = pd.to_datetime(menu.open_date, format='%Y-%m-%d')
                menu = menu.groupby(['date'],
                                    as_index=False).agg({'pa': 'sum'})
            menu = menu.rename(columns={'pa': 'suma'})
            goods['date'] = goods.open_date.dt.strftime('%Y-%m-%d')
            goods = pd.merge(goods, menu, on='date')
            goods['Rr'] = goods.pa / goods.suma
            goods = goods.round({'Rr': 2})
            goods.drop(['date'], axis=1, inplace=True)
            menu = goods.copy(deep=True)
            menu.dropna(axis=0, inplace=True)
            menu['profit'] = menu.profit * menu.Rr
            if len(menu):
                menu['date'] = menu.close_date.dt.strftime('%Y-%m-%d')
                menu['date'] = pd.to_datetime(menu.date, format='%Y-%m-%d')
                menu = menu.groupby(['date'], as_index=False).agg({
                    'profit': 'sum',
                    'sa': 'sum',
                    'pv': 'mean'
                })
            return goods, menu
            pass
        except Exception as e:
            ExceptionInfo(e)

    @classmethod
    def order_repeat(cls, details):
        """
        检验交易清单中重复的订单,对于两个个交易订单来说,重复意味着两者的code与open_date相同
        :param details:
        :return:
        """
        try:
            must = [
                'code',
                'open_date',
            ]
            if set(must) <= set(details.columns):
                pass
            else:
                raise Exception(
                    'This details df must have "code" and "open_date"')
            details['f'] = 1
            details = details.groupby(['code', 'open_date'],
                                      as_index=False).agg({'f': 'sum'})
            Rrep = details[details.f > 1].f.sum() / details.f.sum()  # 重复率
            return Rrep, details
            pass
        except Exception as e:
            raise e