Beispiel #1
0
class QA_Backtest_stock_day():

    account = QA_Account()
    market = QA_Market()
    bid = QA_QAMarket_bid()
    setting = QA_Setting()
    clients = setting.client
    user = setting.QA_setting_user_name
    market_data = []

    def __init__(self):

        self.account = QA_Account()
        self.market = QA_Market()
        self.bid = QA_QAMarket_bid()
        self.setting = QA_Setting()
        self.clients = self.setting.client
        self.user = self.setting.QA_setting_user_name

    @classmethod
    def backtest_init(__backtest_cls, func, *arg, **kwargs):
        def __init_backtest(__backtest_cls, *arg, **kwargs):

            __backtest_cls.QA_backtest_init(__backtest_cls)
            func(*arg, **kwargs)
            __backtest_cls.__QA_backtest_init_inside(__backtest_cls)

        return __init_backtest(__backtest_cls)

    @classmethod
    def before_backtest(__backtest_cls, func, *arg, **kwargs):
        # yield __backtest_cls.cash
        func(*arg, **kwargs)
        __backtest_cls.QA_backtest_start(__backtest_cls)

    @classmethod
    def end_backtest(__backtest_cls, func, *arg, **kwargs):
        # yield __backtest_cls.cash
        __backtest_cls.__end_of_backtest(__backtest_cls, func, *arg, **kwargs)
        return func(*arg, **kwargs)

    def __QA_backtest_set_bid_model(self):

        if self.backtest_bid_model == 'market_price':
            self.bid.bid['price'] = 'market_price'
            self.bid.bid['bid_model'] = 'auto'
        elif self.backtest_bid_model == 'close_price':
            self.bid.bid['price'] = 'close_price'
            self.bid.bid['bid_model'] = 'auto'
        elif self.backtest_bid_model == 'strategy':
            self.bid.bid['price'] = 0
            self.bid.bid['bid_model'] = 'strategy'
        else:
            QA_util_log_info('support bid model')
            sys.exit()

    def __QA_backtest_set_save_model(self):
        pass

    def QA_backtest_init(self):
        """既然是被当做装饰器使用,就需要把变量设置放在装饰函数的前面,把函数放在装饰函数的后面"""

        # 设置回测的开始结束时间

        self.strategy_start_date = str('2017-01-05')
        self.strategy_end_date = str('2017-07-01')

        # 设置回测标的,是一个list对象,不过建议只用一个标的
        # gap是回测时,每日获取数据的前推日期(交易日)
        self.strategy_gap = int(60)

        # 设置全局的数据库地址,回测用户名,密码,并初始化
        self.setting.QA_util_sql_mongo_ip = str('127.0.0.1')
        self.setting.QA_setting_user_name = str('admin')
        self.setting.QA_setting_user_password = str('admin')
        self.setting.QA_setting_init()

        # 回测的名字
        self.strategy_name = str('example')

        # 股票的交易日历,真实回测的交易周期,和交易周期在交易日历中的id
        self.trade_list = QA_fetch_trade_date(
            self.setting.client.quantaxis.trade_date)

        self.benchmark_code = 'hs300'
        """
        这里会涉及一个区间的问题,开始时间是要向后推,而结束时间是要向前推,1代表向后推,-1代表向前推
        """
        self.start_real_date = QA_util_get_real_date(self.strategy_start_date,
                                                     self.trade_list, 1)
        self.start_real_id = self.trade_list.index(self.start_real_date)
        self.end_real_date = QA_util_get_real_date(self.strategy_end_date,
                                                   self.trade_list, -1)
        self.end_real_id = self.trade_list.index(self.end_real_date)

        self.strategy_stock_list = ['000001', '000002', '000004']

        self.account.init_assest = 1000000
        self.backtest_bid_model = 'market_price'

    def __QA_backtest_init_inside(self):
        """
        这是模型内部的 初始化,主要是初始化一些账户和市场资产
        写成了私有函数
        @yutiansut
        2017/7/20
        """

        # 重新初始账户资产

        self.setting.QA_setting_init()
        self.account.init()
        # 重新初始化账户的cookie
        self.account.account_cookie = str(random.random())
        # 初始化股票池的市场数据
        self.market_data = QA_fetch_stocklist_day(
            self.strategy_stock_list, self.setting.client.quantaxis.stock_day,
            [
                self.trade_list[self.start_real_id - int(self.strategy_gap)],
                self.trade_list[self.end_real_id]
            ])

    def QA_backtest_start(self, *args, **kwargs):
        """
        这个是回测流程开始的入口

        """
        assert len(self.strategy_stock_list) > 0
        assert len(self.trade_list) > 0
        assert isinstance(self.start_real_date, str)
        assert isinstance(self.end_real_date, str)
        #self.__QA_backtest_init_inside()

        assert len(self.market_data) == len(self.strategy_stock_list)

        QA_util_log_info('QUANTAXIS Backtest Engine Initial Successfully')
        QA_util_log_info('Basical Info: \n' + tabulate(
            [[str(__version__), str(self.strategy_name)]],
            headers=('Version', 'Strategy_name')))
        QA_util_log_info('Stock_List: \n' +
                         tabulate([self.strategy_stock_list]))

        # 初始化报价模式
        self.__QA_backtest_set_bid_model(self)
        self.__messages = []
        """
        try:
            # 在末尾增加一个回调给策略
            outside_handle.on_start(self)
        except:
            pass
        # 加载外部策略
        self.__QA_backest_handle_data(outside_handle)

        """

    @classmethod
    def load_strategy(__backtest_cls, func, *arg, **kwargs):
        '策略加载函数'

        # 首先判断是否能满足回测的要求`
        _info = {}
        _info['stock_list'] = __backtest_cls.strategy_stock_list
        __messages = {}
        __backtest_cls.__init_cash_per_stock = int(
            float(__backtest_cls.account.init_assest) /
            len(__backtest_cls.strategy_stock_list))

        # 策略的交易日循环
        for i in range(int(__backtest_cls.start_real_id),
                       int(__backtest_cls.end_real_id) - 1, 1):
            __backtest_cls.running_date = __backtest_cls.trade_list[i]
            QA_util_log_info(
                '=================daily hold list====================')
            QA_util_log_info('in the begining of ' +
                             __backtest_cls.running_date)
            QA_util_log_info(
                tabulate(
                    __backtest_cls.account.message['body']['account']['hold']))
            func(*arg, **kwargs)

        #最后一天
        __backtest_cls.__end_of_trading(__backtest_cls)

    def __start_of_every_day(self, func, *arg, **kwargs):
        func(self, *arg, **kwargs)

    def __end_of_every_day(self, func, *arg, **kwargs):
        func(self, *arg, **kwargs)

    def __backtest_every_day_trading(self, i, func, *arg, **kwargs):

        # 正在进行的交易日期
        __running_date = self.trade_list[i]
        QA_util_log_info(
            '=================daily hold list====================')
        QA_util_log_info('in the begining of ' + __running_date)
        QA_util_log_info(
            tabulate(self.account.message['body']['account']['hold']))
        for __j in range(0, len(self.strategy_stock_list)):
            if __running_date in [l[6] for l in self.market_data[__j]] and \
                    [l[6] for l in self.market_data[__j]].index(__running_date) \
                    > self.strategy_gap + 1:

                __data = self.__QA_data_handle([
                    __l[6] for __l in self.market_data[__j]
                ].index(__running_date), __j)
                __amount = 0
                for item in __data['account']['body']['account']['hold']:

                    if self.strategy_stock_list[__j] in item:
                        __amount = __amount + item[3]
                if __amount > 0:
                    __hold = 1
                else:
                    __hold = 0

                __result = func(self, *arg, **kwargs)

                if float(self.account.message['body']['account']['cash']
                         [-1]) > 0:
                    self.QA_backtest_excute_bid(
                        __result, __running_date, __hold,
                        str(self.strategy_stock_list[__j])[0:6], __amount)

                else:
                    QA_util_log_info('not enough free money')
            else:
                pass

    def __end_of_trading(self, *arg, **kwargs):
        # 在回测的最后一天,平掉所有仓位(回测的最后一天是不买入的)
        while len(self.account.hold) > 1:
            __hold_list = self.account.hold[1::]
            pre_del_id = []
            for item_ in range(0, len(__hold_list)):
                if __hold_list[item_][3] > 0:
                    __last_bid = self.bid.bid
                    __last_bid['amount'] = int(__hold_list[item_][3])
                    __last_bid['order_id'] = str(random.random())
                    __last_bid['price'] = 'close_price'
                    __last_bid['code'] = str(__hold_list[item_][1])
                    __last_bid['date'] = self.trade_list[self.end_real_id]
                    __last_bid['towards'] = -1
                    __last_bid['user'] = self.setting.QA_setting_user_name
                    __last_bid['strategy'] = self.strategy_name
                    __last_bid['bid_model'] = 'auto'
                    __last_bid['status'] = '0x01'
                    __last_bid['amount_model'] = 'amount'

                    __message = self.market.receive_bid(
                        __last_bid, self.setting.client)
                    _remains_day = 0
                    while __message['header']['status'] == 500:
                        # 停牌状态,这个时候按停牌的最后一天计算价值(假设平仓)

                        __last_bid['date'] = self.trade_list[self.end_real_id -
                                                             _remains_day]
                        _remains_day += 1
                        __message = self.market.receive_bid(
                            __last_bid, self.setting.client)

                        # 直到市场不是为0状态位置,停止前推日期

                    self.__messages = self.account.QA_account_receive_deal(
                        __message)
                else:
                    pre_del_id.append(item_)
            pre_del_id.sort()
            pre_del_id.reverse()
            for item_x in pre_del_id:
                __hold_list.pop(item_x)

    def __end_of_backtest(self, *arg, **kwargs):

        # 开始分析
        QA_util_log_info('start analysis====\n' +
                         str(self.strategy_stock_list))
        QA_util_log_info('=' * 10 + 'Trade History' + '=' * 10)
        QA_util_log_info('\n' +
                         tabulate(self.account.history,
                                  headers=('date', 'code', 'price', 'towards',
                                           'amounts', 'order_id', 'trade_id',
                                           'commission')))
        QA_util_log_info(
            tabulate(self.account.detail,
                     headers=('date', 'code', 'price', 'amounts', 'order_id',
                              'trade_id', 'sell_price', 'sell_order_id',
                              'sell_trade_id', 'sell_date', 'left_amount',
                              'commission')))
        __exist_time = int(self.end_real_id) - int(self.start_real_id) + 1
        self.__benchmark_data = QA_fetch_index_day(self.benchmark_code,
                                                   self.start_real_date,
                                                   self.end_real_date)
        if len(self.__messages) > 1:
            performace = QA_backtest_analysis_start(
                self.setting.client, self.strategy_stock_list, self.__messages,
                self.trade_list[self.start_real_id:self.end_real_id + 1],
                self.market_data, self.__benchmark_data)
            _backtest_mes = {
                'user':
                self.setting.QA_setting_user_name,
                'strategy':
                self.strategy_name,
                'stock_list':
                performace['code'],
                'start_time':
                self.strategy_start_date,
                'end_time':
                self.strategy_end_date,
                'account_cookie':
                self.account.account_cookie,
                'annualized_returns':
                performace['annualized_returns'],
                'benchmark_annualized_returns':
                performace['benchmark_annualized_returns'],
                'assets':
                performace['assets'],
                'benchmark_assets':
                performace['benchmark_assets'],
                'trade_date':
                performace['trade_date'],
                'total_date':
                performace['total_date'],
                'win_rate':
                performace['win_rate'],
                'alpha':
                performace['alpha'],
                'beta':
                performace['beta'],
                'sharpe':
                performace['sharpe'],
                'vol':
                performace['vol'],
                'benchmark_vol':
                performace['benchmark_vol'],
                'max_drop':
                performace['max_drop'],
                'exist':
                __exist_time,
                'time':
                datetime.datetime.now()
            }
            QA_SU_save_backtest_message(_backtest_mes, self.setting.client)
            QA_SU_save_account_message(self.__messages, self.setting.client)
            QA_SU_save_account_to_csv(self.__messages)
        # QA.QA_SU_save_backtest_message(analysis_message, self.setting.client)

    def QA_backtest_excute_bid(self, __result, __date, __hold, __code,
                               __amount):
        """
        这里是处理报价的逻辑部分
        2017/7/19 修改

        """
        """
        2017/8/4再次修改:
        我们需要的是一个简化的报单api


        买入/卖出
        股票代码
        买入/卖出数量
        
        参考实盘的下单接口:
        tradex:
        nCategory - 委托业务的种类
            0 买入
            1 卖出
            2 融资买入
            3 融券卖出
            4 买券还券
            5 卖券还款
            6 现券还券
        nOrderType - 委托报价方式
            0 限价委托; 上海限价委托/ 深圳限价委托
            1 市价委托(深圳对方最优价格)
            2 市价委托(深圳本方最优价格)
            3 市价委托(深圳即时成交剩余撤销)
            4 市价委托(上海五档即成剩撤/ 深圳五档即成剩撤)
            5 市价委托(深圳全额成交或撤销)
            6 市价委托(上海五档即成转限价)
        sAccount - 股东代码
        sStockCode - 证券代码
        sPrice - 价格
        sVolume - 委托证券的股数
        返回值:
        errinfo - 出错时函数抛出的异常信息;
        result - 查询到的数据。


        然后去检查
        1.当前账户是否持仓该股票
        2.当前账户现金是否足以支持购买该股票
        返回
        3.是否委托(委托id)
        4.是否成交(成交id)
        5.成交笔数


        """

        self.__QA_backtest_set_bid_model()
        if self.bid.bid['bid_model'] == 'strategy':
            __bid_price = __result['price']
        else:
            __bid_price = self.bid.bid['price']

        __bid = self.bid.bid

        __bid['order_id'] = str(random.random())
        __bid['user'] = self.setting.QA_setting_user_name
        __bid['strategy'] = self.strategy_name
        __bid['code'] = __code
        __bid['date'] = __date
        __bid['price'] = __bid_price
        __bid['amount'], __bid['amount_model'] = self.__QA_bid_amount(
            __result['amount'], __amount)

        if __result['if_buy'] == 1:
            # 这是买入的情况
            __bid['towards'] = 1
            __message = self.market.receive_bid(__bid, self.setting.client)

            if float(self.account.message['body']['account']['cash'][-1]) > \
                    float(__message['body']['bid']['price']) * \
                    float(__message['body']['bid']['amount']):
                # 这里是买入资金充足的情况
                # 不去考虑
                pass
            else:
                # 如果买入资金不充足,则按照可用资金去买入
                __message['body']['bid']['amount'] = int(
                    float(self.account.message['body']['account']['cash'][-1])
                    / float(
                        float(str(__message['body']['bid']['price'])[0:5]) *
                        100)) * 100

            if __message['body']['bid']['amount'] > 0:
                # 这个判断是为了 如果买入资金不充足,所以买入报了一个0量单的情况
                #如果买入量>0, 才判断为成功交易
                self.account.QA_account_receive_deal(__message)
        elif __result['if_buy'] == 0:
            # 如果买入状态为0,则不进行任何买入操作
            pass

        # 下面是卖出操作,这里在卖出前需要考虑一个是否有仓位的问题:
        # 因为在股票中是不允许卖空操作的,所以这里是股票的交易引擎和期货的交易引擎的不同所在

        if __result['if_sell'] == 1 and __hold == 1:
            __bid['towards'] = -1
            __message = self.market.receive_bid(__bid, self.setting.client)

            self.account.QA_account_receive_deal(__message)

    def __QA_bid_amount(self, __strategy_amount, __amount):
        if __strategy_amount == 'mean':
            return float(
                float(self.account.message['body']['account']['cash'][-1]) /
                len(self.strategy_stock_list)), 'price'
        elif __strategy_amount == 'half':
            return __amount * 0.5, 'amount'
        elif __strategy_amount == 'all':
            return __amount, 'amount'

    def __QA_get_data_from_market(self, __id, stock_id):
        # x=[x[6] for x in self.market_data]
        if __id > self.strategy_gap + 1:
            index_of_day = __id
            index_of_start = index_of_day - self.strategy_gap + 1
            return self.market_data[stock_id][index_of_start:index_of_day + 1]

        # 从账户中更新数据

    def __QA_data_handle(self, __id, __stock_id):
        market_data = self.__QA_get_data_from_market(__id, __stock_id)
        __message = self.account.message

        return {'market': market_data, 'account': __message}
Beispiel #2
0
class QA_Backtest():
    account = QA_Account()
    market = QA_Market()
    bid = QA_QAMarket_bid()
    setting = QA_Setting()
    clients = setting.client
    user = setting.QA_setting_user_name
    """
    backtest 类不应该只是一个简单的构造函数,他应该包含一个回测框架常用的方法和一些定制化的需求实现
    @yutiansut
    2017/6/19


    需要做一个内部的setting表,以dict格式,value是函数名
    @yutiansut
    2017/6/27
    """
    def QA_backtest_load_strategy(self, ):
        __f_strategy_path = self.__backtest_setting['strategy']['file_path']
        __f_strategy_path = __f_strategy_path.split(':')
        if __f_strategy_path[0] == 'file' and __f_strategy_path[1] == 'py':
            if __f_strategy_path[2] == 'local':

                __current_dir = os.getcwd()
                try:
                    if os.path.exists(
                            os.path.exists(
                                str(__current_dir) +
                                '\\backtest_strategy.py')):
                        __file_path = os.path.exists(
                            str(__current_dir) + '\\backtest_strategy.py')

                except:
                    return "wrong with strategy file in current dir"

            elif os.path.exists(__f_strategy_path[2]):
                __file_path = __f_strategy_path[2]
            else:
                QA_util_log_info('error with loading strategy file')
                sys.exit()

        try:
            import __file_path as QUANTAXIS_Strategy
            QA_util_log_info(dir(QUANTAXIS_Strategy))
        except:
            QA_util_log_info('wrong with loading strategy')

    def QA_backtest_import_setting(self, __setting_file_path='10x'):

        if __setting_file_path == '10x':
            __current_dir = os.getcwd()
            try:
                if os.path.exists(
                        os.path.exists(
                            str(__current_dir) + '\\backtest_setting.ini')):
                    __file_path = str(__current_dir) + '\\backtest_setting.ini'

            except:
                return "wrong with config file in current dir"

        elif os.path.exists(__setting_file_path):
            __file_path = os.path.exists(
                str(__current_dir) + '\\backtest_setting.ini')

        self.__backtest_setting = configparser.ConfigParser()
        self.__backtest_setting.read(__file_path)

    def __QA_backtest_set_stock_list(self):
        self.strategy_stock_list = []
        __t_strategy_stock_list = self.__backtest_setting['account'][
            'stock_list']
        __t_strategy_stock_list = __t_strategy_stock_list.split(':')
        if __t_strategy_stock_list[0] == 'file':
            if __t_strategy_stock_list[1] == 'csv':
                if __t_strategy_stock_list[2] == 'local':
                    __current_dir = os.getcwd()
                    try:
                        if os.path.exists(
                                os.path.exists(
                                    str(__current_dir) + '\\stock_list.csv')):
                            __stock_list_file_path = str(__current_dir) + \
                                '\\stock_list.csv'
                        else:
                            QA_util_log_info(
                                "wrong with csv file in current dir, \
                                    the name should be \\stock_list.csv")
                    except:
                        QA_util_log_info("wrong with csv file in current dir, \
                                    the name should be \\stock_list.csv")
                else:
                    try:
                        if os.path.exists(__t_strategy_stock_list[2]):
                            __stock_list_file_path = __t_strategy_stock_list[2]
                        else:
                            QA_util_log_info(
                                "wrong with csv file in current dir, \
                                    the name should be \\stock_list.csv")
                    except:
                        QA_util_log_info("wrong with csv file in current dir, \
                                    the name should be \\stock_list.csv")
                with open(__stock_list_file_path, 'r') as csv_file:
                    __data = csv.reader(csv_file)

                    for item in __data:
                        self.strategy_stock_list.append(item[0])
            elif __t_strategy_stock_list[1] == 'json':
                if __t_strategy_stock_list[2] == 'local':
                    __current_dir = os.getcwd()
                    try:
                        if os.path.exists(
                                os.path.exists(
                                    str(__current_dir) + '\\stock_list.json')):
                            __stock_list_file_path = str(__current_dir) + \
                                '\\stock_list.json'

                    except:
                        return "wrong with csv file in current dir, \
                        the name should be \\stock_list.json"

                else:
                    try:
                        if os.path.exists(__t_strategy_stock_list[2]):
                            __stock_list_file_path = __t_strategy_stock_list[2]
                        else:
                            return "wrong with csv file in current dir, \
                            the name should be \\stock_list.json"

                    except:
                        return "wrong with csv file in current dir, \
                        the name should be \\stock_list.json"

        elif __t_strategy_stock_list[0] == 'mongo':
            try:
                import pymongo

                coll = pymongo.MongoClient().__t_strategy_stock_list[1].split(
                    '-')[0].__t_strategy_stock_list[1].split['-'][1]
                assert isinstance(__t_strategy_stock_list[2], str)
                return coll.find(__t_strategy_stock_list[2])
            except:
                QA_util_log_info(
                    'something wrong with import stock_list from mongodb')
        elif __t_strategy_stock_list[0] == 'data':
            if __t_strategy_stock_list[1] == 'list':
                self.strategy_stock_list = __t_strategy_stock_list[2]

    def __QA_backtest_set_bid_model(self):
        if self.__backtest_setting['bid']['bid_model'] == 'market_price':
            self.bid.bid['price'] = 'market_price'
            self.bid.bid['bid_model'] = 'auto'
        elif self.__backtest_setting['bid']['bid_model'] == 'close_price':
            self.bid.bid['price'] = 'close_price'
            self.bid.bid['bid_model'] = 'auto'
        elif self.__backtest_setting['bid']['bid_model'] == 'strategy':
            self.bid.bid['price'] = 0
            self.bid.bid['bid_model'] = 'strategy'
        else:
            QA_util_log_info('support bid model')
            sys.exit()

    def __QA_backtest_set_save_model(self):
        pass

    def QA_backtest_init(self):
        # 设置回测的开始结束时间

        try:
            self.QA_backtest_import_setting()
        except:
            sys.exit()

        self.strategy_start_date = str(
            self.__backtest_setting['backtest']['strategy_start_date'])
        self.strategy_end_date = str(
            self.__backtest_setting['backtest']['strategy_end_date'])

        # 设置回测标的,是一个list对象,不过建议只用一个标的
        # gap是回测时,每日获取数据的前推日期(交易日)
        self.strategy_gap = int(
            self.__backtest_setting['backtest']['strategy_gap'])

        # 设置全局的数据库地址,回测用户名,密码,并初始化
        self.setting.QA_util_sql_mongo_ip = str(
            self.__backtest_setting['backtest']['database_ip'])
        self.setting.QA_setting_user_name = str(
            self.__backtest_setting['backtest']['username'])
        self.setting.QA_setting_user_password = str(
            self.__backtest_setting['backtest']['password'])
        self.setting.QA_setting_init()

        # 回测的名字
        self.strategy_name = str(
            self.__backtest_setting['backtest']['strategy_name'])

        # 股票的交易日历,真实回测的交易周期,和交易周期在交易日历中的id
        self.trade_list = QA_fetch_trade_date(
            self.setting.client.quantaxis.trade_date)

        self.benchmark_code = self.__backtest_setting['backtest'][
            'benchmark_code']
        """
        这里会涉及一个区间的问题,开始时间是要向后推,而结束时间是要向前推,1代表向后推,-1代表向前推
        """
        self.start_real_date = QA_util_get_real_date(self.strategy_start_date,
                                                     self.trade_list, 1)
        self.start_real_id = self.trade_list.index(self.start_real_date)
        self.end_real_date = QA_util_get_real_date(self.strategy_end_date,
                                                   self.trade_list, -1)
        self.end_real_id = self.trade_list.index(self.end_real_date)

        self.__QA_backtest_set_stock_list()

        self.account.init_assest = self.__backtest_setting['account'][
            'account_assets']

    def __QA_backtest_init_inside(self):
        """
        这是模型内部的 初始化,主要是初始化一些账户和市场资产
        写成了私有函数
        @yutiansut
        2017/7/20
        """

        # 重新初始账户资产
        self.account.init()
        # 重新初始化账户的cookie
        self.account.account_cookie = str(random.random())
        # 初始化股票池的市场数据
        self.market_data = QA_fetch_stocklist_day(
            self.strategy_stock_list, self.setting.client.quantaxis.stock_day,
            [
                self.trade_list[self.start_real_id - int(self.strategy_gap)],
                self.trade_list[self.end_real_id]
            ])

    def QA_backtest_start(self, outside_handle, *args, **kwargs):
        """
        这个是回测流程开始的入口

        """
        assert len(self.strategy_stock_list) > 0
        assert len(self.trade_list) > 0
        assert isinstance(self.start_real_date, str)
        assert isinstance(self.end_real_date, str)
        self.__QA_backtest_init_inside()

        assert len(self.market_data) == len(self.strategy_stock_list)

        QA_util_log_info('QUANTAXIS Backtest Engine Initial Successfully')
        QA_util_log_info('Basical Info: \n' + tabulate(
            [[str(__version__), str(self.strategy_name)]],
            headers=('Version', 'Strategy_name')))
        QA_util_log_info('Stock_List: \n' +
                         tabulate([self.strategy_stock_list]))

        # 初始化报价模式
        self.__QA_backtest_set_bid_model()
        try:
            # 在末尾增加一个回调给策略
            outside_handle.on_start(self)
        except:
            pass
        # 加载外部策略
        self.__QA_backest_handle_data(outside_handle)

    def __QA_backest_handle_data(self, outside_handle):
        '这个outside_handle就是一个外部的注入函数'

        # 首先判断是否能满足回测的要求`
        _info = {}
        _info['stock_list'] = self.strategy_stock_list
        __messages = {}
        self.__init_cash_per_stock = int(
            float(self.account.init_assest) / len(self.strategy_stock_list))

        # 策略的交易日循环
        for i in range(int(self.start_real_id), int(self.end_real_id) - 1, 1):
            # 正在进行的交易日期
            __running_date = self.trade_list[i]
            QA_util_log_info(
                '=================daily hold list====================')
            QA_util_log_info('in the begining of ' + __running_date)
            QA_util_log_info(
                tabulate(self.account.message['body']['account']['hold']))
            for __j in range(0, len(self.strategy_stock_list)):
                if __running_date in [l[6] for l in self.market_data[__j]] and \
                        [l[6] for l in self.market_data[__j]].index(__running_date) \
                        > self.strategy_gap + 1:

                    __data = self.__QA_data_handle([
                        __l[6] for __l in self.market_data[__j]
                    ].index(__running_date), __j)
                    __amount = 0
                    for item in __data['account']['body']['account']['hold']:

                        if self.strategy_stock_list[__j] in item:
                            __amount = __amount + item[3]
                    if __amount > 0:
                        __hold = 1
                    else:
                        __hold = 0

                    __result = outside_handle.predict(__data['market'],
                                                      __data['account'],
                                                      __hold, _info)

                    if float(self.account.message['body']['account']['cash']
                             [-1]) > 0:
                        self.__QA_backtest_excute_bid(
                            __result, __running_date, __hold,
                            str(self.strategy_stock_list[__j])[0:6], __amount)

                    else:
                        QA_util_log_info('not enough free money')
                else:
                    pass

        # 在回测的最后一天,平掉所有仓位(回测的最后一天是不买入的)
        while len(self.account.hold) > 1:
            __hold_list = self.account.hold[1::]
            pre_del_id = []
            for item_ in range(0, len(__hold_list)):
                if __hold_list[item_][3] > 0:
                    __last_bid = self.bid.bid
                    __last_bid['amount'] = int(__hold_list[item_][3])
                    __last_bid['order_id'] = str(random.random())
                    __last_bid['price'] = 'close_price'
                    __last_bid['code'] = str(__hold_list[item_][1])
                    __last_bid['date'] = self.trade_list[self.end_real_id]
                    __last_bid['towards'] = -1
                    __last_bid['user'] = self.setting.QA_setting_user_name
                    __last_bid['strategy'] = self.strategy_name
                    __last_bid['bid_model'] = 'auto'
                    __last_bid['status'] = '0x01'
                    __last_bid['amount_model'] = 'amount'

                    __message = self.market.receive_bid(
                        __last_bid, self.setting.client)
                    _remains_day = 0
                    while __message['header']['status'] == 500:
                        # 停牌状态,这个时候按停牌的最后一天计算价值(假设平仓)

                        __last_bid['date'] = self.trade_list[self.end_real_id -
                                                             _remains_day]
                        _remains_day += 1
                        __message = self.market.receive_bid(
                            __last_bid, self.setting.client)

                        # 直到市场不是为0状态位置,停止前推日期

                    __messages = self.account.QA_account_receive_deal(
                        __message)
                else:
                    pre_del_id.append(item_)
            pre_del_id.sort()
            pre_del_id.reverse()
            for item_x in pre_del_id:
                __hold_list.pop(item_x)

        try:
            # 在末尾增加一个回调给策略
            outside_handle.on_end(__data['market'], __data['account'], __hold,
                                  _info)
        except:
            pass
        # 开始分析
        QA_util_log_info('start analysis====\n' +
                         str(self.strategy_stock_list))
        QA_util_log_info('=' * 10 + 'Trade History' + '=' * 10)
        QA_util_log_info('\n' +
                         tabulate(self.account.history,
                                  headers=('date', 'code', 'price', 'towards',
                                           'amounts', 'order_id', 'trade_id',
                                           'commission')))
        QA_util_log_info(
            tabulate(self.account.detail,
                     headers=('date', 'code', 'price', 'amounts', 'order_id',
                              'trade_id', 'sell_price', 'sell_order_id',
                              'sell_trade_id', 'sell_date', 'left_amount',
                              'commission')))
        __exist_time = int(self.end_real_id) - int(self.start_real_id) + 1
        self.__benchmark_data = QA_fetch_index_day(self.benchmark_code,
                                                   self.start_real_date,
                                                   self.end_real_date)

        performace = QA_backtest_analysis_start(
            self.setting.client, self.strategy_stock_list, __messages,
            self.trade_list[self.start_real_id:self.end_real_id + 1],
            self.market_data, self.__benchmark_data)
        _backtest_mes = {
            'user':
            self.setting.QA_setting_user_name,
            'strategy':
            self.strategy_name,
            'stock_list':
            performace['code'],
            'start_time':
            self.strategy_start_date,
            'end_time':
            self.strategy_end_date,
            'account_cookie':
            self.account.account_cookie,
            'annualized_returns':
            performace['annualized_returns'],
            'benchmark_annualized_returns':
            performace['benchmark_annualized_returns'],
            'assets':
            performace['assets'],
            'benchmark_assets':
            performace['benchmark_assets'],
            'trade_date':
            performace['trade_date'],
            'total_date':
            performace['total_date'],
            'win_rate':
            performace['win_rate'],
            'alpha':
            performace['alpha'],
            'beta':
            performace['beta'],
            'sharpe':
            performace['sharpe'],
            'vol':
            performace['vol'],
            'benchmark_vol':
            performace['benchmark_vol'],
            'max_drop':
            performace['max_drop'],
            'exist':
            __exist_time,
            'time':
            datetime.datetime.now()
        }

        QA_SU_save_backtest_message(_backtest_mes, self.setting.client)
        QA_SU_save_account_message(__messages, self.setting.client)
        QA_SU_save_account_to_csv(__messages)
        # QA.QA_SU_save_backtest_message(analysis_message, self.setting.client)

    def __QA_backtest_excute_bid(self, __result, __date, __hold, __code,
                                 __amount):
        """
        这里是处理报价的逻辑部分
        2017/7/19 修改

        __result传进来的变量重新区分: 现在需要有 if_buy, if_sell
        因为需要对于: 持仓状态下继续购买进行进一步的支持*简单的情形就是  浮盈加仓

        if_buy, if_sell都需要传入

        现在的 买卖状态 和 持仓状态 是解耦的
        """

        # 为了兼容性考虑,我们会在开始的时候检查是否有这些变量
        if 'if_buy' not in list(__result.keys()):
            __result['if_buy'] = 0

        if 'if_sell' not in list(__result.keys()):
            __result['if_sell'] = 0

        self.__QA_backtest_set_bid_model()
        if self.bid.bid['bid_model'] == 'strategy':
            __bid_price = __result['price']
        else:
            __bid_price = self.bid.bid['price']

        __bid = self.bid.bid

        __bid['order_id'] = str(random.random())
        __bid['user'] = self.setting.QA_setting_user_name
        __bid['strategy'] = self.strategy_name
        __bid['code'] = __code
        __bid['date'] = __date
        __bid['price'] = __bid_price
        __bid['amount'], __bid['amount_model'] = self.__QA_bid_amount(
            __result['amount'], __amount)

        if __result['if_buy'] == 1:
            # 这是买入的情况
            __bid['towards'] = 1
            __message = self.market.receive_bid(__bid, self.setting.client)

            if float(self.account.message['body']['account']['cash'][-1]) > \
                    float(__message['body']['bid']['price']) * \
                    float(__message['body']['bid']['amount']):
                # 这里是买入资金充足的情况
                # 不去考虑
                pass
            else:
                # 如果买入资金不充足,则按照可用资金去买入
                __message['body']['bid']['amount'] = int(
                    float(self.account.message['body']['account']['cash'][-1])
                    / float(
                        float(str(__message['body']['bid']['price'])[0:5]) *
                        100)) * 100

            if __message['body']['bid']['amount'] > 0:
                # 这个判断是为了 如果买入资金不充足,所以买入报了一个0量单的情况
                #如果买入量>0, 才判断为成功交易
                self.account.QA_account_receive_deal(__message)
        elif __result['if_buy'] == 0:
            # 如果买入状态为0,则不进行任何买入操作
            pass

        # 下面是卖出操作,这里在卖出前需要考虑一个是否有仓位的问题:
        # 因为在股票中是不允许卖空操作的,所以这里是股票的交易引擎和期货的交易引擎的不同所在

        if __result['if_sell'] == 1 and __hold == 1:
            __bid['towards'] = -1
            __message = self.market.receive_bid(__bid, self.setting.client)

            self.account.QA_account_receive_deal(__message)

    def __QA_bid_amount(self, __strategy_amount, __amount):
        if __strategy_amount == 'mean':
            return float(
                float(self.account.message['body']['account']['cash'][-1]) /
                len(self.strategy_stock_list)), 'price'
        elif __strategy_amount == 'half':
            return __amount * 0.5, 'amount'
        elif __strategy_amount == 'all':
            return __amount, 'amount'

    def __QA_get_data_from_market(self, __id, stock_id):
        # x=[x[6] for x in self.market_data]
        if __id > self.strategy_gap + 1:
            index_of_day = __id
            index_of_start = index_of_day - self.strategy_gap + 1
            return self.market_data[stock_id][index_of_start:index_of_day + 1]

        # 从账户中更新数据

    def __QA_data_handle(self, __id, __stock_id):
        market_data = self.__QA_get_data_from_market(__id, __stock_id)
        __message = self.account.message

        return {'market': market_data, 'account': __message}
class QA_Backtest_stock_day():
    '因为涉及很多不继承类的情况,所以先单列出来'
    account = QA_Account()
    market = QA_Market()
    bid = QA_QAMarket_bid()
    setting = QA_Setting()
    clients = setting.client
    user = setting.QA_setting_user_name
    market_data = []
    today = ''

    def __init__(self):

        self.account = QA_Account()
        self.market = QA_Market()
        self.bid = QA_QAMarket_bid()
        self.setting = QA_Setting()
        self.clients = self.setting.client
        self.user = self.setting.QA_setting_user_name
        self.market_data = []
        self.today = ''

    def __QA_backtest_init(self):
        """既然是被当做装饰器使用,就需要把变量设置放在装饰函数的前面,把函数放在装饰函数的后面"""

        # 设置回测的开始结束时间
        self.strategy_start_date = str('2017-01-05')
        self.strategy_end_date = str('2017-07-01')
        # 设置回测标的,是一个list对象,不过建议只用一个标的
        # gap是回测时,每日获取数据的前推日期(交易日)
        self.strategy_gap = int(60)
        # 设置全局的数据库地址,回测用户名,密码,并初始化
        self.setting.QA_util_sql_mongo_ip = str('127.0.0.1')
        self.setting.QA_setting_user_name = str('admin')
        self.setting.QA_setting_user_password = str('admin')
        self.setting.QA_setting_init()
        # 回测的名字
        self.strategy_name = str('example')
        # 股票的交易日历,真实回测的交易周期,和交易周期在交易日历中的id
        self.trade_list = QA_fetch_trade_date(
            self.setting.client.quantaxis.trade_date)
        self.benchmark_code = 'hs300'
        """
        这里会涉及一个区间的问题,开始时间是要向后推,而结束时间是要向前推,1代表向后推,-1代表向前推
        """

        self.strategy_stock_list = ['000001', '000002', '000004']
        self.account.init_assest = 1000000
        self.backtest_bid_model = 'market_price'

    def __QA_backtest_init_class(self):
        """
        这是模型内部的 初始化,主要是初始化一些账户和市场资产
        写成了私有函数
        @yutiansut
        2017/7/20
        """

        # 重新初始账户资产

        self.setting.QA_setting_init()
        self.account.init()
        self.start_real_date = QA_util_get_real_date(self.strategy_start_date,
                                                     self.trade_list, 1)
        self.start_real_id = self.trade_list.index(self.start_real_date)
        self.end_real_date = QA_util_get_real_date(self.strategy_end_date,
                                                   self.trade_list, -1)
        self.end_real_id = self.trade_list.index(self.end_real_date)
        # 重新初始化账户的cookie
        self.account.account_cookie = str(random.random())
        # 初始化股票池的市场数据
        self.market_data = QA_fetch_stocklist_day(
            self.strategy_stock_list, self.setting.client.quantaxis.stock_day,
            [
                self.trade_list[self.start_real_id - int(self.strategy_gap)],
                self.trade_list[self.end_real_id]
            ])

    def __QA_backtest_start(self, *args, **kwargs):
        """
        这个是回测流程开始的入口
        """
        assert len(self.strategy_stock_list) > 0
        assert len(self.trade_list) > 0
        assert isinstance(self.start_real_date, str)
        assert isinstance(self.end_real_date, str)

        assert len(self.market_data) == len(self.strategy_stock_list)

        QA_util_log_info('QUANTAXIS Backtest Engine Initial Successfully')
        QA_util_log_info('Basical Info: \n' + tabulate(
            [[str(__version__), str(self.strategy_name)]],
            headers=('Version', 'Strategy_name')))
        QA_util_log_info('Stock_List: \n' +
                         tabulate([self.strategy_stock_list]))

        # 初始化报价模式
        self.__QA_backtest_set_bid_model(self)
        self.__messages = []

    def __QA_backtest_set_bid_model(self):

        if self.backtest_bid_model == 'market_price':
            self.bid.bid['price'] = 'market_price'
            self.bid.bid['bid_model'] = 'auto'
        elif self.backtest_bid_model == 'close_price':
            self.bid.bid['price'] = 'close_price'
            self.bid.bid['bid_model'] = 'auto'
        elif self.backtest_bid_model == 'strategy':
            self.bid.bid['price'] = 0
            self.bid.bid['bid_model'] = 'strategy'
        else:
            QA_util_log_info('support bid model')
            sys.exit()

    def __check_state(self, bid_price, bid_amount):
        pass

    def __QA_bid_amount(self, __strategy_amount, __amount):
        if __strategy_amount == 'mean':
            return float(
                float(self.account.message['body']['account']['cash'][-1]) /
                len(self.strategy_stock_list)), 'price'
        elif __strategy_amount == 'half':
            return __amount * 0.5, 'amount'
        elif __strategy_amount == 'all':
            return __amount, 'amount'

    def __QA_get_data_from_market(self, __id, stock_id):
        if __id > self.strategy_gap + 1:
            index_of_day = __id
            index_of_start = index_of_day - self.strategy_gap + 1
            return self.market_data[stock_id][index_of_start:index_of_day + 1]
        else:
            return self.market_data[stock_id][0:__id + 1]

    def __QA_data_handle(self, __id, __stock_id):
        "已经废弃"
        market_data = self.__QA_get_data_from_market(__id, __stock_id)
        __message = self.account.message

        return {'market': market_data, 'account': __message}

    def __backtest_every_day_trading(self, i, func, *arg, **kwargs):

        # 正在进行的交易日期
        __running_date = self.trade_list[i]
        QA_util_log_info(
            '=================daily hold list====================')
        QA_util_log_info('in the begining of ' + __running_date)
        QA_util_log_info(
            tabulate(self.account.message['body']['account']['hold']))
        for __j in range(0, len(self.strategy_stock_list)):
            if __running_date in [l[6] for l in self.market_data[__j]] and \
                    [l[6] for l in self.market_data[__j]].index(__running_date) \
                    > self.strategy_gap + 1:

                __data = self.__QA_data_handle([
                    __l[6] for __l in self.market_data[__j]
                ].index(__running_date), __j)
                __amount = 0
                for item in __data['account']['body']['account']['hold']:

                    if self.strategy_stock_list[__j] in item:
                        __amount = __amount + item[3]
                if __amount > 0:
                    __hold = 1
                else:
                    __hold = 0

                __result = func(self, *arg, **kwargs)

                if float(self.account.message['body']['account']['cash']
                         [-1]) > 0:
                    self.QA_backtest_excute_bid(
                        __result, __running_date, __hold,
                        str(self.strategy_stock_list[__j])[0:6], __amount)

                else:
                    QA_util_log_info('not enough free money')
            else:
                pass

    def __end_of_trading(self, *arg, **kwargs):
        # 在回测的最后一天,平掉所有仓位(回测的最后一天是不买入的)
        # 回测最后一天的交易处理

        while len(self.account.hold) > 1:
            __hold_list = self.account.hold[1::]
            pre_del_id = []
            for item_ in range(0, len(__hold_list)):
                if __hold_list[item_][3] > 0:
                    __last_bid = self.bid.bid
                    __last_bid['amount'] = int(__hold_list[item_][3])
                    __last_bid['order_id'] = str(random.random())
                    __last_bid['price'] = 'close_price'
                    __last_bid['code'] = str(__hold_list[item_][1])
                    __last_bid['date'] = self.trade_list[self.end_real_id]
                    __last_bid['towards'] = -1
                    __last_bid['user'] = self.setting.QA_setting_user_name
                    __last_bid['strategy'] = self.strategy_name
                    __last_bid['bid_model'] = 'auto'
                    __last_bid['status'] = '0x01'
                    __last_bid['amount_model'] = 'amount'

                    __message = self.market.receive_bid(
                        __last_bid, self.setting.client)
                    _remains_day = 0
                    while __message['header']['status'] == 500:
                        # 停牌状态,这个时候按停牌的最后一天计算价值(假设平仓)

                        __last_bid['date'] = self.trade_list[self.end_real_id -
                                                             _remains_day]
                        _remains_day += 1
                        __message = self.market.receive_bid(
                            __last_bid, self.setting.client)

                        # 直到市场不是为0状态位置,停止前推日期

                    self.__messages = self.account.QA_account_receive_deal(
                        __message)
                else:
                    pre_del_id.append(item_)
            pre_del_id.sort()
            pre_del_id.reverse()
            for item_x in pre_del_id:
                __hold_list.pop(item_x)

    def __end_of_backtest(self, *arg, **kwargs):

        # 开始分析
        QA_util_log_info('start analysis====\n' +
                         str(self.strategy_stock_list))
        QA_util_log_info('=' * 10 + 'Trade History' + '=' * 10)
        QA_util_log_info('\n' +
                         tabulate(self.account.history,
                                  headers=('date', 'code', 'price', 'towards',
                                           'amounts', 'order_id', 'trade_id',
                                           'commission')))
        QA_util_log_info(
            tabulate(self.account.detail,
                     headers=('date', 'code', 'price', 'amounts', 'order_id',
                              'trade_id', 'sell_price', 'sell_order_id',
                              'sell_trade_id', 'sell_date', 'left_amount',
                              'commission')))
        __exist_time = int(self.end_real_id) - int(self.start_real_id) + 1
        self.__benchmark_data = QA_fetch_index_day(self.benchmark_code,
                                                   self.start_real_date,
                                                   self.end_real_date)
        if len(self.__messages) > 1:
            performace = QA_backtest_analysis_start(
                self.setting.client, self.strategy_stock_list, self.__messages,
                self.trade_list[self.start_real_id:self.end_real_id + 1],
                self.market_data, self.__benchmark_data)
            _backtest_mes = {
                'user':
                self.setting.QA_setting_user_name,
                'strategy':
                self.strategy_name,
                'stock_list':
                performace['code'],
                'start_time':
                self.strategy_start_date,
                'end_time':
                self.strategy_end_date,
                'account_cookie':
                self.account.account_cookie,
                'annualized_returns':
                performace['annualized_returns'],
                'benchmark_annualized_returns':
                performace['benchmark_annualized_returns'],
                'assets':
                performace['assets'],
                'benchmark_assets':
                performace['benchmark_assets'],
                'trade_date':
                performace['trade_date'],
                'total_date':
                performace['total_date'],
                'win_rate':
                performace['win_rate'],
                'alpha':
                performace['alpha'],
                'beta':
                performace['beta'],
                'sharpe':
                performace['sharpe'],
                'vol':
                performace['vol'],
                'benchmark_vol':
                performace['benchmark_vol'],
                'max_drop':
                performace['max_drop'],
                'exist':
                __exist_time,
                'time':
                datetime.datetime.now()
            }
            QA_SU_save_backtest_message(_backtest_mes, self.setting.client)
            QA_SU_save_account_message(self.__messages, self.setting.client)
            QA_SU_save_account_to_csv(self.__messages)
        # QA.QA_SU_save_backtest_message(analysis_message, self.setting.client)

    def QA_backtest_get_market_data(self, code, date):
        '这个函数封装了关于获取的方式'
        index_of_date = 0
        index_of_code = self.strategy_stock_list.index(code)
        if date in [l[6] for l in self.market_data[index_of_code]]:
            index_of_date = [l[6] for l in self.market_data[index_of_code]
                             ].index(date)
        return self.__QA_get_data_from_market(self, index_of_date,
                                              index_of_code)

    def QA_backtest_hold_amount(self, __code):
        __amount_hold = 0
        for item in self.account.hold:

            if __code in item:
                __amount_hold += item[3]
        return __amount_hold

    def QA_backtest_get_OHLCV(self, __data):
        '快速返回 OHLCV格式'
        return (__data.T[1].astype(float).tolist(),
                __data.T[2].astype(float).tolist(),
                __data.T[3].astype(float).tolist(),
                __data.T[4].astype(float).tolist(),
                __data.T[5].astype(float).tolist())

    def QA_backtest_send_order(self, __code: str, __amount: int,
                               __towards: int, __order: dict):
        """
        2017/8/4
        委托函数
        在外部封装的一个报价接口,尽量满足和实盘一样的模式

        输入
        =============
        买入/卖出
        股票代码
        买入/卖出数量
        委托模式*
            0 限价委托 LIMIT ORDER
            1 市价委托 MARKET ORDER
            2 严格模式(买入按最高价 卖出按最低价) STRICT ORDER


        输出
        =============
        返回: 

        委托状态/委托id

        成交状态/成交id/成交量/成交价

        错误/错误id/

        return bid_status,trade_status,error
        """

        # 必须是100股的倍数
        __amount = int(__amount / 100) * 100

        # self.__QA_backtest_set_bid_model()
        if __order['bid_model'] in [
                'limit', 'Limit', 'Limited', 'limited', 'l', 'L', 0, '0'
        ]:
            # 限价委托模式
            __bid_price = __order['price']
        elif __order['bid_model'] in [
                'Market', 'market', 'MARKET', 'm', 'M', 1, '1'
        ]:
            __bid_price = 'market_price'
        elif __order['bid_model'] in ['strict', 'Strict', 's', 'S', '2', 2]:
            __bid_price = 'strict_price'
        elif __order['bid_model'] in [
                'close', 'close_price', 'c', 'C', '3', 3
        ]:
            __bid_price = 'close_price'
        __bid = self.bid.bid

        __bid['order_id'] = str(random.random())
        __bid['user'] = self.setting.QA_setting_user_name
        __bid['strategy'] = self.strategy_name
        __bid['code'] = __code
        __bid['date'] = self.running_date
        __bid['price'] = __bid_price
        __bid['amount'] = __amount

        if __towards == 1:
            # 这是买入的情况 买入的时候主要考虑的是能不能/有没有足够的钱来买入

            __bid['towards'] = 1
            __message = self.market.receive_bid(__bid, self.setting.client)

            # 先扔进去买入,再通过返回的值来判定是否成功

            if float(self.account.message['body']['account']['cash'][-1]) > \
                    float(__message['body']['bid']['price']) * \
                    float(__message['body']['bid']['amount']):
                # 这里是买入资金充足的情况
                # 不去考虑
                pass
            else:
                # 如果买入资金不充足,则按照可用资金去买入
                # 这里可以这样做的原因是在买入的时候 手续费为0
                __message['body']['bid']['amount'] = int(
                    float(self.account.message['body']['account']['cash'][-1])
                    / float(
                        float(str(__message['body']['bid']['price'])[0:5]) *
                        100)) * 100

            if __message['body']['bid']['amount'] > 0:
                # 这个判断是为了 如果买入资金不充足,所以买入报了一个0量单的情况
                #如果买入量>0, 才判断为成功交易
                self.account.QA_account_receive_deal(__message)
                return __message

        # 下面是卖出操作,这里在卖出前需要考虑一个是否有仓位的问题:
        # 因为在股票中是不允许卖空操作的,所以这里是股票的交易引擎和期货的交易引擎的不同所在

        elif __towards == -1:
            # 如果是卖出操作 检查是否有持仓
            # 股票中不允许有卖空操作
            # 检查持仓面板
            __amount_hold = self.QA_backtest_hold_amount(self, __code)
            if __amount_hold > 0:
                __bid['towards'] = -1
                if __amount_hold >= __amount:
                    pass
                else:
                    __bid['amount'] = __amount_hold
                __message = self.market.receive_bid(__bid, self.setting.client)
                if __message['header']['status'] == 200:
                    self.account.QA_account_receive_deal(__message)
                return __message
            else:
                err_info = 'Error: Not Enough amount for code %s in hold list' % str(
                    __code)
                QA_util_log_expection(err_info)
                return err_info

        else:
            return "Error: No buy/sell towards"

    def QA_backtest_sell_all(self):
        while len(self.account.hold) > 1:
            __hold_list = self.account.hold[1::]
            pre_del_id = []
            for item_ in range(0, len(__hold_list)):
                if __hold_list[item_][3] > 0:
                    __last_bid = self.bid.bid
                    __last_bid['amount'] = int(__hold_list[item_][3])
                    __last_bid['order_id'] = str(random.random())
                    __last_bid['price'] = 'close_price'
                    __last_bid['code'] = str(__hold_list[item_][1])
                    __last_bid['date'] = self.today
                    __last_bid['towards'] = -1
                    __last_bid['user'] = self.setting.QA_setting_user_name
                    __last_bid['strategy'] = self.strategy_name
                    __last_bid['bid_model'] = 'auto'
                    __last_bid['status'] = '0x01'
                    __last_bid['amount_model'] = 'amount'

                    __message = self.market.receive_bid(
                        __last_bid, self.setting.client)
                    _remains_day = 0
                    while __message['header']['status'] == 500:
                        # 停牌状态,这个时候按停牌的最后一天计算价值(假设平仓)

                        __last_bid['date'] = self.trade_list[self.end_real_id -
                                                             _remains_day]
                        _remains_day += 1
                        __message = self.market.receive_bid(
                            __last_bid, self.setting.client)

                        # 直到市场不是为0状态位置,停止前推日期

                    self.__messages = self.account.QA_account_receive_deal(
                        __message)
                else:
                    pre_del_id.append(item_)
            pre_del_id.sort()
            pre_del_id.reverse()
            for item_x in pre_del_id:
                __hold_list.pop(item_x)

    @classmethod
    def load_strategy(__backtest_cls, func, *arg, **kwargs):
        '策略加载函数'

        # 首先判断是否能满足回测的要求`
        _info = {}
        _info['stock_list'] = __backtest_cls.strategy_stock_list
        __messages = {}
        __backtest_cls.__init_cash_per_stock = int(
            float(__backtest_cls.account.init_assest) /
            len(__backtest_cls.strategy_stock_list))

        # 策略的交易日循环
        for i in range(int(__backtest_cls.start_real_id),
                       int(__backtest_cls.end_real_id) - 1, 1):
            __backtest_cls.running_date = __backtest_cls.trade_list[i]
            QA_util_log_info(
                '=================daily hold list====================')
            QA_util_log_info('in the begining of ' +
                             __backtest_cls.running_date)
            QA_util_log_info(
                tabulate(
                    __backtest_cls.account.message['body']['account']['hold']))
            __backtest_cls.today = __backtest_cls.running_date

            func(*arg, **kwargs)

        # 最后一天
        __backtest_cls.__end_of_trading(__backtest_cls)

    @classmethod
    def backtest_init(__backtest_cls, func, *arg, **kwargs):
        def __init_backtest(__backtest_cls, *arg, **kwargs):
            __backtest_cls.__QA_backtest_init(__backtest_cls)
            func(*arg, **kwargs)
            __backtest_cls.__QA_backtest_init_class(__backtest_cls)

        return __init_backtest(__backtest_cls)

    @classmethod
    def before_backtest(__backtest_cls, func, *arg, **kwargs):
        func(*arg, **kwargs)
        __backtest_cls.__QA_backtest_start(__backtest_cls)

    @classmethod
    def end_backtest(__backtest_cls, func, *arg, **kwargs):
        # yield __backtest_cls.cash
        __backtest_cls.__end_of_backtest(__backtest_cls, func, *arg, **kwargs)
        return func(*arg, **kwargs)
class QA_Backtest_stock_day():
    '最终目的还是实现一个通用的回测类'
    backtest_type = 'day'
    account = QA_Account()
    market = QA_Market()
    bid = QA_QAMarket_bid()
    setting = QA_Setting()
    clients = setting.client
    user = setting.QA_setting_user_name
    market_data = []
    now = ''
    today = ''

    def __init__(self):
        self.backtest_type = 'day'
        self.account = QA_Account()
        self.market = QA_Market()
        self.bid = QA_QAMarket_bid()
        self.setting = QA_Setting()
        self.clients = self.setting.client
        self.user = self.setting.QA_setting_user_name
        self.market_data = []
        self.now = ''
        self.today = ''

    def __QA_backtest_init(self):
        """既然是被当做装饰器使用,就需要把变量设置放在装饰函数的前面,把函数放在装饰函数的后面"""

        # 设置回测的开始结束时间
        self.strategy_start_date = str('2017-01-05')
        self.strategy_end_date = str('2017-07-01')
        # 设置回测标的,是一个list对象,不过建议只用一个标的
        # gap是回测时,每日获取数据的前推日期(交易日)
        self.strategy_gap = int(60)
        # 设置全局的数据库地址,回测用户名,密码,并初始化
        self.setting.QA_util_sql_mongo_ip = str('127.0.0.1')
        self.setting.QA_setting_user_name = str('admin')
        self.setting.QA_setting_user_password = str('admin')
        self.setting.QA_setting_init()
        # 回测的名字
        self.strategy_name = str('example_min')
       # 股票的交易日历,真实回测的交易周期,和交易周期在交易日历中的id
        self.trade_list = QA_fetch_trade_date(
            self.setting.client.quantaxis.trade_date)
        self.benchmark_code = 'hs300'
        """
        这里会涉及一个区间的问题,开始时间是要向后推,而结束时间是要向前推,1代表向后推,-1代表向前推
        """

        self.strategy_stock_list = ['000001', '000002', '000004']
        self.account.init_assest = 1000000
        self.backtest_bid_model = 'market_price'

    def __QA_backtest_prepare(self):
        """
        这是模型内部的 初始化,主要是初始化一些账户和市场资产
        写成了私有函数
        @yutiansut
        2017/7/20
        """

        # 重新初始账户资产

        self.setting.QA_setting_init()
        self.account.init()
        self.start_real_date = QA_util_get_real_date(
            self.strategy_start_date, self.trade_list, 1)
        self.start_real_id = self.trade_list.index(self.start_real_date)
        self.end_real_date = QA_util_get_real_date(
            self.strategy_end_date, self.trade_list, -1)
        self.end_real_id = self.trade_list.index(self.end_real_date)
        # 重新初始化账户的cookie
        self.account.account_cookie = str(random.random())
        # 初始化股票池的市场数据

        if self.backtest_type in ['day', 'd', '0x00']:
            self.market_data = QA_fetch_stocklist_day(
                self.strategy_stock_list,
                [self.trade_list[self.start_real_id - int(self.strategy_gap)],
                 self.trade_list[self.end_real_id]])
        elif self.backtest_type in ['min', 'm', '0x01']:
            self.market_data = QA_fetch_stocklist_min(
                self.strategy_stock_list, [self.trade_list[
                    self.start_real_id - int(self.strategy_gap)],
                    self.trade_list[self.end_real_id]])

    def __QA_backtest_start(self, *args, **kwargs):
        """
        这个是回测流程开始的入口
        """
        assert len(self.strategy_stock_list) > 0
        assert len(self.trade_list) > 0
        assert isinstance(self.start_real_date, str)
        assert isinstance(self.end_real_date, str)
        assert len(self.market_data) == len(self.strategy_stock_list)

        QA_util_log_info('QUANTAXIS Backtest Engine Initial Successfully')
        QA_util_log_info('Basical Info: \n' + tabulate(
            [[str(__version__), str(self.strategy_name)]], headers=('Version', 'Strategy_name')))
        QA_util_log_info('BACKTEST Cookie_ID is:  ' +
                         str(self.account.account_cookie))
        QA_util_log_info('Stock_List: \n' +
                         tabulate([self.strategy_stock_list]))

        # 初始化报价模式
        self.__QA_backtest_set_bid_model(self)
        self.__messages = []

    def __QA_backtest_set_bid_model(self):

        if self.backtest_bid_model == 'market_price':
            self.bid.price = 'market_price'
            self.bid.bid_model = 'auto'
        elif self.backtest_bid_model == 'close_price':
            self.bid.price = 'close_price'
            self.bid.bid_model = 'auto'
        elif self.backtest_bid_model == 'strategy':
            self.bid.price = 0
            self.bid.bid_model = 'strategy'
        else:
            QA_util_log_info('support bid model')
            sys.exit()

    def __check_state(self, bid_price, bid_amount):
        pass

    def __QA_bid_amount(self, __strategy_amount, __amount):
        if __strategy_amount == 'mean':
            return float(float(self.account.message['body']['account']['cash'][-1]) /
                         len(self.strategy_stock_list)), 'price'
        elif __strategy_amount == 'half':
            return __amount * 0.5, 'amount'
        elif __strategy_amount == 'all':
            return __amount, 'amount'

    def __end_of_trading(self, *arg, **kwargs):
        # 在回测的最后一天,平掉所有仓位(回测的最后一天是不买入的)
        # 回测最后一天的交易处理

        while len(self.account.hold) > 1:
            __hold_list = self.account.hold[1::]
            pre_del_id = []
            for item_ in range(0, len(__hold_list)):
                if __hold_list[item_][3] > 0:
                    __last_bid = self.bid
                    __last_bid.amount = int(__hold_list[item_][3])
                    __last_bid.order_id = str(random.random())
                    __last_bid.price = 'close_price'
                    __last_bid.code = str(__hold_list[item_][1])
                    __last_bid.date = self.trade_list[self.end_real_id]
                    __last_bid.towards = -1
                    __last_bid.user = self.setting.QA_setting_user_name
                    __last_bid.strategy = self.strategy_name
                    __last_bid.bid_model = 'auto'
                    __last_bid.type = '0x01'
                    __last_bid.amount_model = 'amount'

                    __message = self.market.receive_bid(
                        __last_bid)
                    _remains_day = 0
                    while __message['header']['status'] == 500:
                        # 停牌状态,这个时候按停牌的最后一天计算价值(假设平仓)

                        __last_bid.date = self.trade_list[self.end_real_id - _remains_day]
                        _remains_day += 1
                        __message = self.market.receive_bid(
                            __last_bid)

                        # 直到市场不是为0状态位置,停止前推日期

                    self.__messages = self.account.QA_account_receive_deal(
                        __message)
                else:
                    pre_del_id.append(item_)
            pre_del_id.sort()
            pre_del_id.reverse()
            for item_x in pre_del_id:
                __hold_list.pop(item_x)

    def __end_of_backtest(self, *arg, **kwargs):

        # 开始分析
        QA_util_log_info('start analysis====\n' +
                         str(self.strategy_stock_list))
        QA_util_log_info('=' * 10 + 'Trade History' + '=' * 10)
        QA_util_log_info('\n' + tabulate(self.account.history,
                                         headers=('date', 'code', 'price', 'towards',
                                                  'amounts', 'order_id', 'trade_id', 'commission')))
        QA_util_log_info('\n' + tabulate(self.account.detail,
                                         headers=('date', 'code', 'price', 'amounts', 'order_id',
                                                  'trade_id', 'sell_price', 'sell_order_id',
                                                  'sell_trade_id', 'sell_date', 'left_amount',
                                                  'commission')))
        __exist_time = int(self.end_real_id) - int(self.start_real_id) + 1
        self.__benchmark_data = QA_fetch_index_day(
            self.benchmark_code, self.start_real_date,
            self.end_real_date)
        if len(self.__messages) > 1:
            performace = QA_backtest_analysis_start(
                self.setting.client, self.strategy_stock_list, self.__messages,
                self.trade_list[self.start_real_id:self.end_real_id + 1],
                self.market_data, self.__benchmark_data)
            _backtest_mes = {
                'user': self.setting.QA_setting_user_name,
                'strategy': self.strategy_name,
                'stock_list': performace['code'],
                'start_time': self.strategy_start_date,
                'end_time': self.strategy_end_date,
                'account_cookie': self.account.account_cookie,
                'annualized_returns': performace['annualized_returns'],
                'benchmark_annualized_returns': performace['benchmark_annualized_returns'],
                'assets': performace['assets'],
                'benchmark_assets': performace['benchmark_assets'],
                'trade_date': performace['trade_date'],
                'total_date': performace['total_date'],
                'win_rate': performace['win_rate'],
                'alpha': performace['alpha'],
                'beta': performace['beta'],
                'sharpe': performace['sharpe'],
                'vol': performace['vol'],
                'benchmark_vol': performace['benchmark_vol'],
                'max_drop': performace['max_drop'],
                'exist': __exist_time,
                'time': datetime.datetime.now()
            }
            QA_SU_save_backtest_message(_backtest_mes, self.setting.client)
            QA_SU_save_account_message(self.__messages, self.setting.client)
            QA_SU_save_account_to_csv(self.__messages)
        # QA.QA_SU_save_backtest_message(analysis_message, self.setting.client)

    def QA_backtest_get_market_data(self, code, date, type_='numpy'):
        '这个函数封装了关于获取的方式'
        index_of_code = self.strategy_stock_list.index(code)
        __res = self.market_data[index_of_code][:date].tail(self.strategy_gap)
        if type_ in ['l', 'list', 'L']:
            return np.asarray(__res).tolist()
        elif type_ in ['pd', 'pandas', 'p']:
            return __res
        else:
            return np.asarray(__res)

    def QA_backtest_hold_amount(self, __code):
        return sum(list(map(lambda item: item[3] if __code in item else 0, self.account.hold)))

    def QA_backtest_get_OHLCV(self, __data):
        '快速返回 OHLCV格式'
        return (__data.T[1].astype(float).tolist(), __data.T[2].astype(float).tolist(),
                __data.T[3].astype(float).tolist(
        ), __data.T[4].astype(float).tolist(),
            __data.T[5].astype(float).tolist())

    def QA_backtest_send_order(self, __code, __amount, __towards, __order):
        """
        2017/8/4
        委托函数
        在外部封装的一个报价接口,尽量满足和实盘一样的模式

        输入
        =============
        买入/卖出
        股票代码
        买入/卖出数量
        委托模式*
            0 限价委托 LIMIT ORDER
            1 市价委托 MARKET ORDER
            2 严格模式(买入按最高价 卖出按最低价) STRICT ORDER


        输出
        =============
        返回: 

        委托状态/委托id

        成交状态/成交id/成交量/成交价

        错误/错误id/

        return bid_status,trade_status,error
        """

        # 必须是100股的倍数
        __amount = int(__amount / 100) * 100

        # self.__QA_backtest_set_bid_model()
        if __order['bid_model'] in ['limit', 'Limit', 'Limited', 'limited', 'l', 'L', 0, '0']:
            # 限价委托模式
            __bid_price = __order['price']
        elif __order['bid_model'] in ['Market', 'market', 'MARKET', 'm', 'M', 1, '1']:
            __bid_price = 'market_price'
        elif __order['bid_model'] in ['strict', 'Strict', 's', 'S', '2', 2]:
            __bid_price = 'strict_price'
        elif __order['bid_model'] in ['close', 'close_price', 'c', 'C', '3', 3]:
            __bid_price = 'close_price'
        __bid = self.bid

        __bid.order_id = str(random.random())
        __bid.user = self.setting.QA_setting_user_name
        __bid.strategy = self.strategy_name
        __bid.code = __code
        __bid.date = self.running_date
        __bid.datetime = self.running_date
        __bid.sending_time = self.running_date
        __bid.price = __bid_price
        __bid.amount = __amount

        if __towards == 1:
            # 这是买入的情况 买入的时候主要考虑的是能不能/有没有足够的钱来买入

            __bid.towards = 1
            __message = self.market.receive_bid(
                __bid)

            # 先扔进去买入,再通过返回的值来判定是否成功

            if float(self.account.message['body']['account']['cash'][-1]) > \
                    float(__message['body']['bid']['price']) * \
                    float(__message['body']['bid']['amount']):
                    # 这里是买入资金充足的情况
                    # 不去考虑
                pass
            else:
                # 如果买入资金不充足,则按照可用资金去买入
                # 这里可以这样做的原因是在买入的时候 手续费为0
                __message['body']['bid']['amount'] = int(float(
                    self.account.message['body']['account']['cash'][-1]) / float(
                        float(str(__message['body']['bid']['price'])[0:5]) * 100)) * 100

            if __message['body']['bid']['amount'] > 0:
                # 这个判断是为了 如果买入资金不充足,所以买入报了一个0量单的情况
                #如果买入量>0, 才判断为成功交易
                self.messages=self.account.QA_account_receive_deal(__message)
                return __message

        # 下面是卖出操作,这里在卖出前需要考虑一个是否有仓位的问题:
        # 因为在股票中是不允许卖空操作的,所以这里是股票的交易引擎和期货的交易引擎的不同所在

        elif __towards == -1:
            # 如果是卖出操作 检查是否有持仓
            # 股票中不允许有卖空操作
            # 检查持仓面板
            __amount_hold = self.QA_backtest_hold_amount(self, __code)
            if __amount_hold > 0:
                __bid.towards = -1
                __bid.amount = __amount_hold if __amount_hold < __amount else __bid.amount
                __message = self.market.receive_bid(
                    __bid)
                if __message['header']['status'] == 200:
                    self.messages=self.account.QA_account_receive_deal(__message)
                return __message
            else:
                err_info = 'Error: Not Enough amount for code %s in hold list' % str(
                    __code)
                QA_util_log_expection(err_info)
                return err_info

        else:
            return "Error: No buy/sell towards"

    def QA_backtest_check_order(self, order):
        '用于检查委托单的状态'
        """
        委托单被报入交易所会有一个回报,回报状态就是交易所返回的字段:
        字段目前 2xx 是成功  4xx是失败 5xx是交易所无数据(停牌)

        随着回测框架的不断升级,会有更多状态需要被管理:


        200 委托成功,交易成功
        203 委托成功,待成交
        20
        """
        pass

    def QA_backtest_sell_all(self):
        while len(self.account.hold) > 1:
            __hold_list = self.account.hold[1::]
            pre_del_id = []

            def __sell(id_):
                if __hold_list[id_][3] > 0:
                    __last_bid = self.bid
                    __last_bid.amount = int(__hold_list[id_][3])
                    __last_bid.order_id = str(random.random())
                    __last_bid.price = 'close_price'
                    __last_bid.code = str(__hold_list[id_][1])
                    __last_bid.date = self.now
                    __last_bid.towards = -1
                    __last_bid.user = self.setting.QA_setting_user_name
                    __last_bid.strategy = self.strategy_name
                    __last_bid.bid_model = 'auto'
                    __last_bid.type = '0x01'
                    __last_bid.amount_model = 'amount'

                    __message = self.market.receive_bid(
                        __last_bid)
                    _remains_day = 0
                    while __message['header']['status'] == 500:
                        # 停牌状态,这个时候按停牌的最后一天计算价值(假设平仓)

                        __last_bid.date = self.trade_list[self.end_real_id - _remains_day]
                        _remains_day += 1
                        __message = self.market.receive_bid(
                            __last_bid)

                        # 直到市场不是为0状态位置,停止前推日期

                    self.__messages = self.account.QA_account_receive_deal(
                        __message)
                else:
                    pre_del_id.append(id_)
                return pre_del_id

            pre_del_id = reduce(lambda _, x: __sell(x),
                                range(len(__hold_list)))
            pre_del_id.sort()
            pre_del_id.reverse()
            for item_x in pre_del_id:
                __hold_list.pop(item_x)

    @classmethod
    def load_strategy(__backtest_cls, func, *arg, **kwargs):
        '策略加载函数'

        # 首先判断是否能满足回测的要求`
        __messages = {}
        __backtest_cls.__init_cash_per_stock = int(
            float(__backtest_cls.account.init_assest) / len(__backtest_cls.strategy_stock_list))

        # 策略的交易日循环
        for i in range(int(__backtest_cls.start_real_id), int(__backtest_cls.end_real_id) - 1, 1):
            __backtest_cls.running_date = __backtest_cls.trade_list[i]
            QA_util_log_info(
                '=================daily hold list====================')
            QA_util_log_info('in the begining of ' +
                             __backtest_cls.running_date)
            QA_util_log_info(
                tabulate(__backtest_cls.account.message['body']['account']['hold']))
            __backtest_cls.now = __backtest_cls.running_date
            __backtest_cls.today = __backtest_cls.running_date
            func(*arg, **kwargs)

        # 最后一天
        __backtest_cls.__end_of_trading(__backtest_cls)

    @classmethod
    def backtest_init(__backtest_cls, func, *arg, **kwargs):
        def __init_backtest(__backtest_cls, *arg, **kwargs):
            __backtest_cls.__QA_backtest_init(__backtest_cls)
            func(*arg, **kwargs)
            __backtest_cls.__QA_backtest_prepare(__backtest_cls)
        return __init_backtest(__backtest_cls)

    @classmethod
    def before_backtest(__backtest_cls, func, *arg, **kwargs):
        func(*arg, **kwargs)
        __backtest_cls.__QA_backtest_start(__backtest_cls)

    @classmethod
    def end_backtest(__backtest_cls, func, *arg, **kwargs):
        # yield __backtest_cls.cash
        __backtest_cls.__end_of_backtest(__backtest_cls, func, *arg, **kwargs)
        return func(*arg, **kwargs)
Beispiel #5
0
class QA_Backtest():
    '最终目的还是实现一个通用的回测类'
    backtest_type = 'day'
    account = QA_Account()
    market = QA_Market()
    bid = QA_QAMarket_bid()
    setting = QA_Setting()
    clients = setting.client
    user = setting.QA_setting_user_name
    market_data = []
    now = ''
    today = ''

    def __init__(self):
        self.backtest_type = 'day'
        self.account = QA_Account()
        self.market = QA_Market()
        self.bid = QA_QAMarket_bid()
        self.setting = QA_Setting()
        self.clients = self.setting.client
        self.user = self.setting.QA_setting_user_name
        self.market_data = []
        self.now = ''
        self.today = ''

    def __QA_backtest_init(self):
        """既然是被当做装饰器使用,就需要把变量设置放在装饰函数的前面,把函数放在装饰函数的后面"""

        # 设置回测的开始结束时间
        self.strategy_start_date = str('2017-01-05')
        self.strategy_end_date = str('2017-07-01')
        # 设置回测标的,是一个list对象,不过建议只用一个标的
        # gap是回测时,每日获取数据的前推日期(交易日)
        self.strategy_gap = int(60)
        # 设置全局的数据库地址,回测用户名,密码,并初始化
        self.setting.QA_util_sql_mongo_ip = str('127.0.0.1')
        self.setting.QA_setting_user_name = str('admin')
        self.setting.QA_setting_user_password = str('admin')
        self.setting.QA_setting_init()
        # 回测的名字
        self.strategy_name = str('example_min')
        # 股票的交易日历,真实回测的交易周期,和交易周期在交易日历中的id
        self.trade_list = QA_fetch_trade_date(
            self.setting.client.quantaxis.trade_date)
        self.benchmark_code = 'hs300'
        """
        这里会涉及一个区间的问题,开始时间是要向后推,而结束时间是要向前推,1代表向后推,-1代表向前推
        """

        self.strategy_stock_list = ['000001', '000002', '000004']
        self.account.init_assest = 1000000
        self.backtest_bid_model = 'market_price'

    def __QA_backtest_prepare(self):
        """
        这是模型内部的 初始化,主要是初始化一些账户和市场资产
        写成了私有函数
        @yutiansut
        2017/7/20
        """

        # 重新初始账户资产

        self.setting.QA_setting_init()
        self.account.init()
        self.start_real_date = QA_util_get_real_date(self.strategy_start_date,
                                                     self.trade_list, 1)
        self.start_real_id = self.trade_list.index(self.start_real_date)
        self.end_real_date = QA_util_get_real_date(self.strategy_end_date,
                                                   self.trade_list, -1)
        self.end_real_id = self.trade_list.index(self.end_real_date)
        # 重新初始化账户的cookie
        self.account.account_cookie = str(random.random())
        # 初始化股票池的市场数据

        if self.backtest_type in ['day', 'd', '0x00']:
            self.market_data = QA_fetch_stocklist_day(
                self.strategy_stock_list, [
                    self.trade_list[self.start_real_id -
                                    int(self.strategy_gap)],
                    self.trade_list[self.end_real_id]
                ])
        elif self.backtest_type in ['min', 'm', '0x01']:
            self.market_data = QA_fetch_stocklist_min(
                self.strategy_stock_list, [
                    self.trade_list[self.start_real_id -
                                    int(self.strategy_gap)],
                    self.trade_list[self.end_real_id]
                ])

    def __QA_backtest_start(self, *args, **kwargs):
        """
        这个是回测流程开始的入口
        """
        assert len(self.strategy_stock_list) > 0
        assert len(self.trade_list) > 0
        assert isinstance(self.start_real_date, str)
        assert isinstance(self.end_real_date, str)
        assert len(self.market_data) == len(self.strategy_stock_list)

        QA_util_log_info('QUANTAXIS Backtest Engine Initial Successfully')
        QA_util_log_info('Basical Info: \n' + tabulate(
            [[str(__version__), str(self.strategy_name)]],
            headers=('Version', 'Strategy_name')))
        QA_util_log_info('Stock_List: \n' +
                         tabulate([self.strategy_stock_list]))

        # 初始化报价模式
        self.__messages = []

    def __check_state(self, bid_price, bid_amount):
        pass

    def __QA_bid_amount(self, __strategy_amount, __amount):
        if __strategy_amount == 'mean':
            return float(
                float(self.account.message['body']['account']['cash'][-1]) /
                len(self.strategy_stock_list)), 'price'
        elif __strategy_amount == 'half':
            return __amount * 0.5, 'amount'
        elif __strategy_amount == 'all':
            return __amount, 'amount'

    def __end_of_trading(self, *arg, **kwargs):
        # 在回测的最后一天,平掉所有仓位(回测的最后一天是不买入的)
        # 回测最后一天的交易处理

        while len(self.account.hold) > 1:
            __hold_list = self.account.hold[1::]
            pre_del_id = []
            for item_ in range(0, len(__hold_list)):
                if __hold_list[item_][3] > 0:
                    __last_bid = self.bid
                    __last_bid.amount = int(__hold_list[item_][3])
                    __last_bid.order_id = str(random.random())
                    __last_bid.price = 'close_price'
                    __last_bid.code = str(__hold_list[item_][1])
                    __last_bid.date = self.trade_list[self.end_real_id]
                    __last_bid.towards = -1
                    __last_bid.user = self.setting.QA_setting_user_name
                    __last_bid.strategy = self.strategy_name
                    __last_bid.bid_model = 'auto'
                    __last_bid.status = '0x01'
                    __last_bid.amount_model = 'amount'

                    __message = self.market.receive_bid(__last_bid)
                    _remains_day = 0
                    while __message['header']['status'] == 500:
                        # 停牌状态,这个时候按停牌的最后一天计算价值(假设平仓)

                        __last_bid.date = self.trade_list[self.end_real_id -
                                                          _remains_day]
                        _remains_day += 1
                        __message = self.market.receive_bid(__last_bid)

                        # 直到市场不是为0状态位置,停止前推日期

                    self.__messages = self.account.QA_account_receive_deal(
                        __message)
                else:
                    pre_del_id.append(item_)
            pre_del_id.sort()
            pre_del_id.reverse()
            for item_x in pre_del_id:
                __hold_list.pop(item_x)

    def __warp_bid(self, __bid, __order):
        __market_data_for_backtest = self.QA_backtest_get_market_data(
            self, __bid.code, __bid.date, 'np', 1)
        __O, __H, __L, __C, __V = self.QA_backtest_get_OHLCV(
            self, __market_data_for_backtest)
        if __order['bid_model'] in [
                'limit', 'Limit', 'Limited', 'limited', 'l', 'L', 0, '0'
        ]:
            # 限价委托模式
            __bid.price = __order['price']
        elif __order['bid_model'] in [
                'Market', 'market', 'MARKET', 'm', 'M', 1, '1'
        ]:
            __bid.price = 0.5 * (float(__O[0]) + float(__C[0]))
        elif __order['bid_model'] in ['strict', 'Strict', 's', 'S', '2', 2]:
            __bid.price = float(__H[0]) if __bid.towards == 1 else float(
                __L[0])
        elif __order['bid_model'] in [
                'close', 'close_price', 'c', 'C', '3', 3
        ]:
            __bid.price = float(__C[0])

        __bid.price = float('%.2f' % __bid.price)

        return __bid

    def __end_of_backtest(self, *arg, **kwargs):

        # 开始分析
        QA_util_log_info('start analysis====\n' +
                         str(self.strategy_stock_list))
        QA_util_log_info('=' * 10 + 'Trade History' + '=' * 10)
        QA_util_log_info('\n' +
                         tabulate(self.account.history,
                                  headers=('date', 'code', 'price', 'towards',
                                           'amounts', 'order_id', 'trade_id',
                                           'commission')))
        QA_util_log_info('\n' +
                         tabulate(self.account.detail,
                                  headers=('date', 'code', 'price', 'amounts',
                                           'order_id', 'trade_id',
                                           'sell_price', 'sell_order_id',
                                           'sell_trade_id', 'sell_date',
                                           'left_amount', 'commission')))
        __exist_time = int(self.end_real_id) - int(self.start_real_id) + 1
        self.__benchmark_data = QA_fetch_index_day(self.benchmark_code,
                                                   self.start_real_date,
                                                   self.end_real_date)
        if len(self.__messages) > 1:
            performace = QA_backtest_analysis_start(
                self.setting.client, self.strategy_stock_list, self.__messages,
                self.trade_list[self.start_real_id:self.end_real_id + 1],
                self.market_data, self.__benchmark_data)
            _backtest_mes = {
                'user':
                self.setting.QA_setting_user_name,
                'strategy':
                self.strategy_name,
                'stock_list':
                performace['code'],
                'start_time':
                self.strategy_start_date,
                'end_time':
                self.strategy_end_date,
                'account_cookie':
                self.account.account_cookie,
                'annualized_returns':
                performace['annualized_returns'],
                'benchmark_annualized_returns':
                performace['benchmark_annualized_returns'],
                'assets':
                performace['assets'],
                'benchmark_assets':
                performace['benchmark_assets'],
                'trade_date':
                performace['trade_date'],
                'total_date':
                performace['total_date'],
                'win_rate':
                performace['win_rate'],
                'alpha':
                performace['alpha'],
                'beta':
                performace['beta'],
                'sharpe':
                performace['sharpe'],
                'vol':
                performace['vol'],
                'benchmark_vol':
                performace['benchmark_vol'],
                'max_drop':
                performace['max_drop'],
                'exist':
                __exist_time,
                'time':
                datetime.datetime.now()
            }
            QA_SU_save_backtest_message(_backtest_mes, self.setting.client)
            QA_SU_save_account_message(self.__messages, self.setting.client)
            QA_SU_save_account_to_csv(self.__messages)

    def QA_backtest_get_market_data(self,
                                    code,
                                    date,
                                    type_='numpy',
                                    gap_=None):
        '这个函数封装了关于获取的方式'
        gap_ = self.strategy_gap if gap_ is None else gap_
        index_of_code = self.strategy_stock_list.index(code)
        __res = self.market_data[index_of_code][:date].tail(gap_)
        if type_ in ['l', 'list', 'L']:
            return np.asarray(__res).tolist()
        elif type_ in ['pd', 'pandas', 'p']:
            return res
        else:
            return np.asarray(__res)

    def QA_backtest_hold_amount(self, __code):
        # return sum(list(map(lambda item: item[3] if __code in item else 0, self.account.hold)))
        try:
            return self.account.hold_available[__code]
        except:
            return 0

    def QA_backtest_get_OHLCV(self, __data):
        '快速返回 OHLCV格式'
        return (__data.T[1].astype(float).tolist(),
                __data.T[2].astype(float).tolist(),
                __data.T[3].astype(float).tolist(),
                __data.T[4].astype(float).tolist(),
                __data.T[5].astype(float).tolist())

    def QA_backtest_send_order(self, __code: str, __amount: int,
                               __towards: int, __order: dict):
        """
        2017/8/4
        委托函数
        在外部封装的一个报价接口,尽量满足和实盘一样的模式

        输入
        =============
        买入/卖出
        股票代码
        买入/卖出数量
        委托模式*
            0 限价委托 LIMIT ORDER
            1 市价委托 MARKET ORDER
            2 严格模式(买入按最高价 卖出按最低价) STRICT ORDER
        """
        # 只需要维护未成交队列即可,无非是一个可用资金和可卖股票数量的调整

        # 必须是100股的倍数
        __amount = int(__amount / 100) * 100

        # self.__QA_backtest_set_bid_model()

        __bid = self.bid

        (__bid.order_id, __bid.user, __bid.strategy, __bid.code, __bid.date,
         __bid.datetime, __bid.sending_time, __bid.amount,
         __bid.towards) = (str(random.random()),
                           self.setting.QA_setting_user_name,
                           self.strategy_name, __code, self.running_date,
                           self.running_date, self.running_date, __amount,
                           __towards)
        __bid = self.__warp_bid(self, __bid, __order)
        return self.__QA_backtest_send_bid(self, __bid)
        # 先进行处理:

    def __QA_backtest_send_bid(self, __bid):
        if __bid.towards == 1:
            # 扣费以下这个订单时的bar的open扣费

            if self.account.cash_available > (__bid.price * __bid.amount):
                # 这是买入的情况 买入的时候主要考虑的是能不能/有没有足够的钱来买入
                __message = self.market.receive_bid(__bid)
                self.account.cash_available -= (__bid.price * __bid.amount)
                # 先扔进去买入,再通过返回的值来判定是否成功
                if __message['header']['status'] == 200 and __message['body'][
                        'bid']['amount'] > 0:
                    # 这个判断是为了 如果买入资金不充足,所以买入报了一个0量单的情况
                    # 如果买入量>0, 才判断为成功交易

                    self.account.QA_account_receive_deal(__message)
                    return __message
                else:

                    # self.account.cash_available += (__bid.price * __bid.amount) #报单还挂着 不能恢复
                    self.account.order_queue.append(__bid)

        # 下面是卖出操作,这里在卖出前需要考虑一个是否有仓位的问题:
        # 因为在股票中是不允许卖空操作的,所以这里是股票的交易引擎和期货的交易引擎的不同所在

        elif __bid.towards == -1:
            # 如果是卖出操作 检查是否有持仓
            # 股票中不允许有卖空操作
            # 检查持仓面板
            __amount_hold = self.QA_backtest_hold_amount(self, __bid.code)

            if __amount_hold > 0:

                __bid.amount = __amount_hold if __amount_hold < __bid.amount else __bid.amount
                self.account.hold_available[__bid.code] -= __bid.amount
                __message = self.market.receive_bid(__bid)

                print(__message)
                if __message['header']['status'] == 200:
                    self.account.QA_account_receive_deal(__message)
                    return __message
                else:
                    self.account.order_queue.append(__bid)
                    return __message

            else:
                err_info = 'Error: Not Enough amount for code %s in hold list' % str(
                    __bid.code)
                QA_util_log_expection(err_info)
                return err_info

        else:
            return "Error: No buy/sell towards"

    def QA_backtest_check_order(self, order):
        '用于检查委托单的状态'
        """
        委托单被报入交易所会有一个回报,回报状态就是交易所返回的字段:
        字段目前 2xx 是成功  4xx是失败 5xx是交易所无数据(停牌)

        随着回测框架的不断升级,会有更多状态需要被管理:


        200 委托成功,交易成功
        203 委托成功,待成交
        20
        """
        pass

    def QA_backtest_sell_all(self):
        while len(self.account.hold) > 1:
            __hold_list = self.account.hold[1::]
            pre_del_id = []

            def __sell(id_):
                if __hold_list[item_][3] > 0:
                    __last_bid = self.bid
                    __last_bid.amount = int(__hold_list[id_][3])
                    __last_bid.order_id = str(random.random())
                    __last_bid.price = 'close_price'
                    __last_bid.code = str(__hold_list[id_][1])
                    __last_bid.date = self.now
                    __last_bid.towards = -1
                    __last_bid.user = self.setting.QA_setting_user_name
                    __last_bid.strategy = self.strategy_name
                    __last_bid.bid_model = 'auto'
                    __last_bid.status = '0x01'
                    __last_bid.amount_model = 'amount'

                    __message = self.market.receive_bid(__last_bid)
                    _remains_day = 0
                    while __message['header']['status'] == 500:
                        # 停牌状态,这个时候按停牌的最后一天计算价值(假设平仓)

                        __last_bid.date = self.trade_list[self.end_real_id -
                                                          _remains_day]
                        _remains_day += 1
                        __message = self.market.receive_bid(__last_bid)

                        # 直到市场不是为0状态位置,停止前推日期

                    self.__messages = self.account.QA_account_receive_deal(
                        __message)
                else:
                    pre_del_id.append(id_)
                return pre_del_id

            pre_del_id = reduce(lambda _, x: __sell(x),
                                range(len(__hold_list)))
            pre_del_id.sort()
            pre_del_id.reverse()
            for item_x in pre_del_id:
                __hold_list.pop(item_x)

    @classmethod
    def load_strategy(__backtest_cls, func, *arg, **kwargs):
        '策略加载函数'

        # 首先判断是否能满足回测的要求`
        __messages = {}
        __backtest_cls.__init_cash_per_stock = int(
            float(__backtest_cls.account.init_assest) /
            len(__backtest_cls.strategy_stock_list))

        # 策略的交易日循环
        for i in range(int(__backtest_cls.start_real_id),
                       int(__backtest_cls.end_real_id) - 1, 1):
            __backtest_cls.running_date = __backtest_cls.trade_list[i]
            QA_util_log_info(
                '=================daily hold list====================')
            QA_util_log_info('in the begining of ' +
                             __backtest_cls.running_date)
            QA_util_log_info(
                tabulate(
                    __backtest_cls.account.message['body']['account']['hold']))
            __backtest_cls.now = __backtest_cls.running_date
            __backtest_cls.today = __backtest_cls.running_date
            # 交易前同步持仓状态
            __backtest_cls.account.cash_available = __backtest_cls.account.cash[
                -1]
            __temp_hold = pd.DataFrame(__backtest_cls.account.hold[1::],
                                       columns=__backtest_cls.account.hold[0])
            __temp_hold = __temp_hold.set_index('code', drop=False)
            __backtest_cls.account.hold_available = __temp_hold[
                'amount'].groupby('code').sum()
            if __backtest_cls.backtest_type in ['day', 'd']:
                for item in __backtest_cls.account.order_queue:

                    item.date, item.datetime = (__backtest_cls.today,
                                                __backtest_cls.now)
                    __backtest_cls.__QA_backtest_send_bid(__backtest_cls, item)
                    # input()
                func(*arg, **kwargs)  # 发委托单

            elif __backtest_cls.backtest_type in ['min', 'm']:
                func(*arg, **kwargs)  # 发委托单

            # 队列循环批量发单

        # 最后一天
        __backtest_cls.__end_of_trading(__backtest_cls)

    @classmethod
    def backtest_init(__backtest_cls, func, *arg, **kwargs):
        def __init_backtest(__backtest_cls, *arg, **kwargs):
            __backtest_cls.__QA_backtest_init(__backtest_cls)
            func(*arg, **kwargs)
            __backtest_cls.__QA_backtest_prepare(__backtest_cls)

        return __init_backtest(__backtest_cls)

    @classmethod
    def before_backtest(__backtest_cls, func, *arg, **kwargs):
        func(*arg, **kwargs)
        __backtest_cls.__QA_backtest_start(__backtest_cls)

    @classmethod
    def end_backtest(__backtest_cls, func, *arg, **kwargs):
        # yield __backtest_cls.cash
        __backtest_cls.__end_of_backtest(__backtest_cls, func, *arg, **kwargs)
        return func(*arg, **kwargs)