Esempio n. 1
0
    def __init__(self):

        self.backtest_type = 'day'
        self.account = QA_Account()
        self.market = QA_Market()
        self.order = QA_QAMarket_bid_list()
        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.strategy_start_date = ''
        self.strategy_start_time = ''
        self.strategy_end_date = ''
        self.strategy_end_time = ''
        self.today = ''
        self.strategy_stock_list = []
        self.trade_list = []
        self.start_real_id = 0
        self.end_real_id = 0
        self.temp = {}
        self.commission_fee_coeff = 0.0015
        self.benchmark_type = 'index'
        self.market_data_dict = {}
        self.backtest_print_log = True  # 打印
Esempio n. 2
0
    def __QA_backtest_prepare(self):
        """
        这是模型内部的 初始化,主要是初始化一些账户和市场资产
        写成了私有函数
        @yutiansut
        2017/7/20
        """
        if len(str(self.strategy_start_date))==10:
            self.strategy_start_time=str(self.strategy_start_date)+' 15:00:00'
        elif len(str(self.strategy_start_date))==19:
            self.strategy_start_time=str(self.strategy_start_date)
            self.strategy_start_date=str(self.strategy_start_date)[0:10]
        else:
            QA_util_log_info('Wrong start date format')

        if len(str(self.strategy_end_date))==10:
            self.strategy_end_time=str(self.strategy_end_date)+' 15:00:00'
        elif len(str(self.strategy_end_date))==19:
            self.strategy_end_time=str(self.strategy_end_date)
            self.strategy_end_date=str(self.strategy_end_date)[0:10]
        else:
            QA_util_log_info('Wrong end date format')
        # 重新初始账户资产
        self.market = QA_Market(self.commission_fee_coeff)
        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_time=str(self.start_real_date)+' '+self.strategy_start_time.split(' ')[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.end_real_time=str(self.end_real_date)+' '+self.strategy_end_time.split(' ')[1]
        # 重新初始化账户的cookie
        self.account.account_cookie = str(random.random())
        # 初始化股票池的市场数据
        if self.benchmark_type in ['I','index']:
            self.benchmark_data = QA_fetch_index_day_adv(
                self.benchmark_code, self.start_real_date, self.end_real_date)
        elif self.benchmark_type in ['S','stock']:
            self.benchmark_data = QA_fetch_stock_day_adv(
                self.benchmark_code, self.start_real_date, self.end_real_date)
        if self.backtest_type in ['day', 'd', '0x00']:
            self.market_data = QA_fetch_stocklist_day_adv(
                self.strategy_stock_list, self.trade_list[self.start_real_id - int(
                    self.strategy_gap+1)], self.trade_list[self.end_real_id]).to_qfq()

        elif self.backtest_type in ['1min', '5min', '15min', '30min', '60min']:
            self.market_data = QA_fetch_stocklist_min_adv(
                self.strategy_stock_list, QA_util_time_gap(self.start_real_time,self.strategy_gap+1,'<',self.backtest_type),
                QA_util_time_gap(self.end_real_time,1,'>',self.backtest_type), self.backtest_type).to_qfq()

        elif self.backtest_type in ['index_day']:
            self.market_data = QA_fetch_index_day_adv(self.strategy_stock_list, self.trade_list[self.start_real_id - int(
                self.strategy_gap+1)], self.end_real_date)

        elif self.backtest_type in ['index_1min', 'index_5min', 'index_15min', 'index_30min', 'index_60min']:
            self.market_data = QA_fetch_index_min_adv(
                self.strategy_stock_list, QA_util_time_gap(self.start_real_time,self.strategy_gap+1,'<',self.backtest_type.split('_')[1]),  QA_util_time_gap(self.end_real_time,1,'>',self.backtest_type.split('_')[1]), self.backtest_type.split('_')[1])
Esempio n. 3
0
    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
Esempio n. 4
0
 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 = ''
Esempio n. 5
0
    def __QA_backtest_prepare(self):
        """
        这是模型内部的 初始化,主要是初始化一些账户和市场资产
        写成了私有函数
        @yutiansut
        2017/7/20
        """

        # 重新初始账户资产
        self.market = QA_Market(self.commission_fee_coeff)
        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.benchmark_data = QA_fetch_index_day_adv(self.benchmark_code,
                                                     self.start_real_date,
                                                     self.end_real_date)
        if self.backtest_type in ['day', 'd', '0x00']:
            self.market_data = QA_fetch_stocklist_day_adv(
                self.strategy_stock_list,
                self.trade_list[self.start_real_id - int(self.strategy_gap)],
                self.trade_list[self.end_real_id]).to_qfq()

        elif self.backtest_type in ['1min', '5min', '15min', '30min', '60min']:
            self.market_data = QA_fetch_stocklist_min_adv(
                self.strategy_stock_list,
                self.trade_list[self.start_real_id - int(self.strategy_gap)],
                self.trade_list[self.end_real_id + 1],
                self.backtest_type).to_qfq()

        elif self.backtest_type in ['index_day']:
            self.market_data = QA_fetch_index_day_adv(
                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 [
                'index_1min', 'index_5min', 'index_15min', 'index_30min',
                'index_60min'
        ]:
            self.market_data = QA_fetch_index_min_adv(
                self.strategy_stock_list, self.start_real_date,
                self.end_real_date,
                self.backtest_type.split('_')[1])
Esempio n. 6
0
    def __init__(self):

        self.backtest_type = 'day'
        self.account = QA_Account()
        self.market = QA_Market()
        self.order = QA_QAMarket_bid_list()
        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 = ''
        self.strategy_stock_list = []
        self.trade_list = []
        self.start_real_id = 0
        self.end_real_id = 0
        self.temp = {}
 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 = ''
Esempio n. 8
0
    def __init__(self):

        self.backtest_type = 'day'
        self.account = QA_Account()
        self.market = QA_Market()
        self.order = QA_QAMarket_bid_list()
        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 = None
        self.last_time = None
        self.strategy_start_date = ''
        self.strategy_start_time = ''
        self.strategy_end_date = ''
        self.strategy_end_time = ''
        self.today = None
        self.strategy_stock_list = []
        self.trade_list = []
        self.start_real_id = 0
        self.end_real_id = 0
        self.temp = {}
        self.commission_fee_coeff = 0.0015
        self.account_d_value = []
        self.account_d_key = []
        self.benchmark_type = 'index'
        self.market_data_dict = {}
        self.backtest_print_log = True  # 打印
        self.if_save_to_mongo = True
        self.if_save_to_csv = True
        self.stratey_version = 'V1'
        self.topic_name = 'EXAMPLE'
        self.topic_id = ''
        self.outside_data = []
        self.outside_data_dict = []
        self.outside_data_hashable = {}
        self.dirs = '.{}QUANTAXIS_RESULT{}{}{}{}{}'.format(
            os.sep, os.sep, self.topic_name, os.sep, self.stratey_version,
            os.sep)
Esempio n. 9
0
    def __init__(self):

        self.backtest_type = 'day'
        self.account = QA_Account()
        self.market = QA_Market()
        self.order = QA_QAMarket_bid_list()
        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 = ''
        self.strategy_stock_list = []
        self.trade_list = []
        self.start_real_id = 0
        self.end_real_id = 0
        self.temp = {}
        self.commission_fee_coeff = 0.0015
Esempio n. 10
0
    def __QA_backtest_prepare(self):
        """
        这是模型内部的 初始化,主要是初始化一些账户和市场资产
        写成了私有函数
        @yutiansut
        2017/7/20
        """

        # 重新初始账户资产
        self.market = QA_Market(self.commission_fee_coeff)
        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.benchmark_data = QA_fetch_index_day_adv(
            self.benchmark_code, self.start_real_date, self.end_real_date)
        if self.backtest_type in ['day', 'd', '0x00']:
            self.market_data = QA_fetch_stocklist_day_adv(
                self.strategy_stock_list, self.trade_list[self.start_real_id - int(
                    self.strategy_gap)], self.trade_list[self.end_real_id]).to_qfq()

        elif self.backtest_type in ['1min', '5min', '15min', '30min', '60min']:
            self.market_data = QA_fetch_stocklist_min_adv(
                self.strategy_stock_list, self.trade_list[
                    self.start_real_id - int(self.strategy_gap)],
                self.trade_list[self.end_real_id + 1], self.backtest_type).to_qfq()

        elif self.backtest_type in ['index_day']:
            self.market_data = QA_fetch_index_day_adv(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 ['index_1min', 'index_5min', 'index_15min', 'index_30min', 'index_60min']:
            self.market_data = QA_fetch_index_min_adv(
                self.strategy_stock_list, self.start_real_date, self.end_real_date, self.backtest_type.split('_')[1])
Esempio n. 11
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

    def QA_backtest_init(self):
        pass

    def QA_backtest_start(self):
        QA_util_log_info('backtest start')

    def QA_backtest_day_start(self):
        pass

    def QA_backtest_handle(self):
        pass

    def QA_backtest_day_end(self):
        pass

    def QA_get_data(self):
        self.QA_get_data_from_market()
        self.QA_get_data_from_ARP()

    def QA_get_data_from_market(self):
        db = self.clients.quantaxis

    def QA_get_data_from_ARP(self):
        pass

    def QA_strategy_update(self):
        pass
Esempio n. 12
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}
Esempio n. 13
0
class QA_Backtest():
    '最终目的还是实现一个通用的回测类'
    backtest_type = 'day'
    account = QA_Account()
    market = QA_Market()
    bid = QA_QAMarket_bid()
    order = QA_QAMarket_bid_list()
    setting = QA_Setting()
    clients = setting.client
    user = setting.QA_setting_user_name
    market_data = []
    now = ''
    today = ''
    strategy_stock_list = []
    trade_list = []
    start_real_id = 0
    end_real_id = 0
    temp = {}
    commission_fee_coeff = 0.0015

    def __init__(self):

        self.backtest_type = 'day'
        self.account = QA_Account()
        self.market = QA_Market()
        self.order = QA_QAMarket_bid_list()
        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 = ''
        self.strategy_stock_list = []
        self.trade_list = []
        self.start_real_id = 0
        self.end_real_id = 0
        self.temp = {}
        self.commission_fee_coeff = 0.0015

    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 = trade_date_sse
        self.benchmark_code = '000300'
        """
        这里会涉及一个区间的问题,开始时间是要向后推,而结束时间是要向前推,1代表向后推,-1代表向前推
        """

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

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

        # 重新初始账户资产
        self.market = QA_Market(self.commission_fee_coeff)
        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.benchmark_data = QA_fetch_index_day_adv(self.benchmark_code,
                                                     self.start_real_date,
                                                     self.end_real_date)
        if self.backtest_type in ['day', 'd', '0x00']:
            self.market_data = QA_fetch_stocklist_day_adv(
                self.strategy_stock_list,
                self.trade_list[self.start_real_id - int(self.strategy_gap)],
                self.trade_list[self.end_real_id]).to_qfq()

        elif self.backtest_type in ['1min', '5min', '15min', '30min', '60min']:
            self.market_data = QA_fetch_stocklist_min_adv(
                self.strategy_stock_list,
                self.trade_list[self.start_real_id - int(self.strategy_gap)],
                self.trade_list[self.end_real_id + 1],
                self.backtest_type).to_qfq()

        elif self.backtest_type in ['index_day']:
            self.market_data = QA_fetch_index_day_adv(
                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 [
                'index_1min', 'index_5min', 'index_15min', 'index_30min',
                'index_60min'
        ]:
            self.market_data = QA_fetch_index_min_adv(
                self.strategy_stock_list, self.start_real_date,
                self.end_real_date,
                self.backtest_type.split('_')[1])

    def __QA_backtest_start(self, *args, **kwargs):
        """
        这个是回测流程开始的入口
        """
        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.__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):
        # 在回测的最后一天,平掉所有仓位(回测的最后一天是不买入的)
        # 回测最后一天的交易处理
        self.now = str(self.end_real_date) + ' 15:00:00'
        self.today = self.end_real_date
        self.QA_backtest_sell_all(self)
        self.__sell_from_order_queue(self)
        self.__sync_order_LM(self, 'daily_settle')  # 每日结算

    def __wrap_bid(self, __bid, __order=None):
        __market_data_for_backtest = self.market_data.get_bar(
            __bid.code, __bid.datetime)
        if __market_data_for_backtest.len() == 1:

            __O, __H, __L, __C, __V = self.QA_backtest_get_OHLCV(
                self, __market_data_for_backtest)
            if __O is not None and __order is not None:
                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'
                ]:
                    # 2017-09-18 修改  市价单以当前bar开盘价下单
                    __bid.price = float(__O[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, __market_data_for_backtest
            else:
                return __bid, __market_data_for_backtest

        else:
            QA_util_log_info(
                'BACKTEST ENGINE ERROR=== CODE %s TIME %s NO MARKET DATA!' %
                (__bid.code, __bid.datetime))
            return __bid, 500

    def __end_of_backtest(self, *arg, **kwargs):
        # 开始分析

        # 对于account.detail做一定的整理
        self.account.detail = detail = pd.DataFrame(
            self.account.detail,
            columns=[
                'date', 'code', 'price', 'amounts', 'order_id', 'trade_id',
                'sell_price', 'sell_order_id', 'sell_trade_id', 'sell_date',
                'left_amount', 'commission'
            ])
        self.account.detail['sell_average'] = self.account.detail[
            'sell_price'].apply(lambda x: mean(x))
        self.account.detail['pnl_persentage'] = self.account.detail['sell_average'] - \
            self.account.detail['price']

        self.account.detail['pnl'] = self.account.detail['pnl_persentage'] * (
            self.account.detail['amounts'] - self.account.detail['left_amount']
        ) - self.account.detail['commission']
        self.account.detail = self.account.detail.drop(
            ['order_id', 'trade_id', 'sell_order_id', 'sell_trade_id'], axis=1)
        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=(self.account.detail.columns)))
        __exist_time = int(self.end_real_id) - int(self.start_real_id) + 1
        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.benchmark_data.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)

            self.account.detail.to_csv('backtest-pnl--' +
                                       str(self.account.account_cookie) +
                                       '.csv')

            # QA_SU_save_pnl_to_csv(self.account.detail,self.account.account_cookie)

    def QA_backtest_get_market_data(self, code, date, gap_=None):
        '这个函数封装了关于获取的方式 用GAP的模式'
        gap_ = self.strategy_gap if gap_ is None else gap_
        return self.market_data.select_code(code).select_time_with_gap(
            date, gap_, 'lte')

    def QA_backtest_get_market_data_bar(self, code, time):
        '这个函数封装了关于获取的方式'
        return self.market_data.get_bar(code, time)

    def QA_backtest_sell_available(self, __code):
        try:
            return self.account.sell_available[__code]
        except:
            return 0

    def QA_backtest_hold_amount(self, __code):
        try:
            return pd.DataFrame(
                self.account.hold[1::],
                columns=self.account.hold[0]).set_index(
                    'code', drop=False)['amount'].groupby('code').sum()[__code]
        except:
            return 0

    def __sell_from_order_queue(self):

        # 每个bar结束的时候,批量交易
        __result = []
        self.order.__init__()
        if len(self.account.order_queue) >= 1:
            __bid_list = self.order.from_dataframe(
                self.account.order_queue.query('status!=200').query(
                    'status!=500').query('status!=400'))

            for item in __bid_list:
                # 在发单的时候要改变交易日期
                item.date = self.today
                item.datetime = self.now

                __bid, __market = self.__wrap_bid(self, item)
                __message = self.__QA_backtest_send_bid(
                    self, __bid,
                    __market.to_json()[0])
                if isinstance(__message, dict):
                    if __message['header']['status'] in ['200', 200]:
                        self.__sync_order_LM(self, 'trade', __bid,
                                             __message['header']['order_id'],
                                             __message['header']['trade_id'],
                                             __message)
                    else:
                        self.__sync_order_LM(self, 'wait')
        else:
            QA_util_log_info('FROM BACKTEST: Order Queue is empty at %s!' %
                             self.now)
            pass

    def QA_backtest_get_OHLCV(self, __data):
        '快速返回 OHLCV格式'
        return (__data.open, __data.high, __data.low, __data.close, __data.vol)

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

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


        功能
        =============
        1. 封装一个bid类(分配地址)
        2. 检查账户/临时扣费
        3. 检查市场(wrap)
        4. 发送到_send_bid方法
        """

        # 必须是100股的倍数
        # 封装bid
        __amount = int(__amount / 100) * 100
        __bid = self.bid  # init
        (__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,
                           str(self.now), self.running_date, __amount,
                           __towards)

        if self.backtest_type in ['day', 'index_day']:
            __bid.type = '0x01'
        elif self.backtest_type in ['1min', '5min', '15min']:
            __bid.type = '0x02'
        # 检查账户/临时扣费

        __bid, __market = self.__wrap_bid(self, __bid, __order)

        if __bid is not None and __market != 500:
            print(
                'GET the Order Code %s Amount %s Price %s Towards %s Time %s' %
                (__bid.code, __bid.amount, __bid.price, __bid.towards,
                 __bid.datetime))
            self.__sync_order_LM(self, 'create_order', order_=__bid)

    def __sync_order_LM(self,
                        event_,
                        order_=None,
                        order_id_=None,
                        trade_id_=None,
                        market_message_=None):
        """
        订单事件: 生命周期管理 Order-Lifecycle-Management
        status1xx 订单待生成
        status3xx 初始化订单  临时扣除资产(可用现金/可卖股份)
        status3xx 订单存活(等待交易)
        status2xx 订单完全交易/未完全交易
        status4xx 主动撤单
        status500 订单死亡(每日结算) 恢复临时资产    
        =======
        1. 更新持仓
        2. 更新现金
        """
        if event_ is 'init_':

            self.account.cash_available = self.account.cash[-1]
            self.account.sell_available = pd.DataFrame(
                self.account.hold[1::],
                columns=self.account.hold[0]).set_index(
                    'code', drop=False)['amount'].groupby('code').sum()

        elif event_ is 'create_order':
            if order_ is not None:
                if order_.towards is 1:
                    # 买入
                    if self.account.cash_available - order_.amount * order_.price > 0:
                        self.account.cash_available -= order_.amount * order_.price
                        order_.status = 300  # 修改订单状态

                        self.account.order_queue = self.account.order_queue.append(
                            order_.to_df())
                elif order_.towards is -1:

                    if self.QA_backtest_sell_available(
                            self, order_.code) - order_.amount >= 0:
                        self.account.sell_available[
                            order_.code] -= order_.amount
                        self.account.order_queue = self.account.order_queue.append(
                            order_.to_df())
            else:
                QA_util_log_info('Order Event Warning:%s in %s' %
                                 (event_, str(self.now)))

        elif event_ in ['wait', 'live']:
            # 订单存活 不会导致任何状态改变
            pass
        elif event_ in ['cancel_order']:  # 订单事件:主动撤单
            # try:
            assert isinstance(order_id_, str)
            self.account.order_queue.loc[
                self.account.order_queue['order_id'] == order_id_,
                'status'] = 400  # 注销事件
            if order_.towards is 1:
                # 多单 撤单  现金增加
                self.account.cash_available += self.account.order_queue.query(
                    'order_id=="order_id_"'
                )['amount'] * self.account.order_queue.query(
                    'order_id=="order_id_"')['price']

            elif order_.towards is -1:
                # 空单撤单 可卖数量增加
                self.account.sell_available[
                    order_.code] += self.account.order_queue.query(
                        'order_id=="order_id_"')['price']
        elif event_ in ['daily_settle']:  # 每日结算/全撤/把成交的买入/卖出单标记为500 同时结转

            # 买入
            """
            每日结算流程
            - 同步实际的现金和仓位
            - 清空留仓单/未成功的订单
            """
            self.account.cash_available = self.account.cash[-1]
            self.account.sell_available = pd.DataFrame(
                self.account.hold[1::],
                columns=self.account.hold[0]).set_index(
                    'code', drop=False)['amount'].groupby('code').sum()

            self.account.order_queue = pd.DataFrame()
        elif event_ in ['t_0']:
            """
            T+0交易事件

            同步t+0的账户状态 /允许卖出
            """
            self.account.cash_available = self.account.cash[-1]
            self.account.sell_available = pd.DataFrame(
                self.account.hold[1::],
                columns=self.account.hold[0]).set_index(
                    'code', drop=False)['amount'].groupby('code').sum()

        elif event_ in ['trade']:
            # try:
            assert isinstance(order_, QA_QAMarket_bid)
            assert isinstance(order_id_, str)
            assert isinstance(trade_id_, str)
            assert isinstance(market_message_, dict)

            if order_.towards is 1:
                # 买入
                # 减少现金
                order_.trade_id = trade_id_
                order_.transact_time = self.now
                order_.amount -= market_message_['body']['bid']['amount']

                if order_.amount == 0:  # 完全交易
                    # 注销(成功交易)['买入单不能立即结转']
                    self.account.order_queue.loc[
                        self.account.order_queue['order_id'] == order_id_,
                        'status'] = 200

                elif order_.amount > 0:
                    # 注销(成功交易)
                    self.account.order_queue.loc[
                        self.account.order_queue['order_id'] == order_id_,
                        'status'] = 203
                    self.account.order_queue.query('order_id=="order_id_"')[
                        'amount'] -= market_message_['body']['bid']['amount']
            elif order_.towards is -1:
                # self.account.sell_available[order_.code] -= market_message_[
                #    'body']['bid']['amount']
                # 当日卖出的股票 可以继续买入/ 可用资金增加(要减去手续费)
                self.account.cash_available += market_message_['body']['bid'][
                    'amount'] * market_message_['body']['bid'][
                        'price'] - market_message_['body']['fee']['commission']
                order_.trade_id = trade_id_
                order_.transact_time = self.now
                order_.amount -= market_message_['body']['bid']['amount']
                if order_.amount == 0:
                    # 注销(成功交易)
                    self.account.order_queue.loc[
                        self.account.order_queue['order_id'] == order_id_,
                        'status'] = 200
                else:
                    # 注销(成功交易)
                    self.account.order_queue.loc[
                        self.account.order_queue['order_id'] == order_id_,
                        'status'] = 203
                    self.account.order_queue[
                        self.account.order_queue['order_id'] ==
                        order_id_]['amount'] -= market_message_['body']['bid'][
                            'amount']
        else:
            QA_util_log_info(
                'EventEngine Warning:Unknown type of order event in  %s' %
                str(self.now))

    def __QA_backtest_send_bid(self, __bid, __market=None):
        __message = self.market.receive_bid(__bid, __market)
        if __bid.towards == 1:
            # 扣费
            # 以下这个订单时的bar的open扣费
            # 先扔进去买入,再通过返回的值来判定是否成功
            if __message['header']['status'] == 200 and __message['body'][
                    'bid']['amount'] > 0:
                # 这个判断是为了 如果买入资金不充足,所以买入报了一个0量单的情况
                # 如果买入量>0, 才判断为成功交易
                QA_util_log_info(
                    'BUY %s Price %s Date %s Amount %s' %
                    (__bid.code, __bid.price, __bid.datetime, __bid.amount))
                self.__messages = self.account.QA_account_receive_deal(
                    __message)
                return __message
            else:

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

        elif __bid.towards == -1:
            # 如果是卖出操作 检查是否有持仓
            # 股票中不允许有卖空操作
            # 检查持仓面板
            if __message['header']['status'] == 200:
                self.__messages = self.account.QA_account_receive_deal(
                    __message)
                QA_util_log_info(
                    'SELL %s Price %s Date %s  Amount %s' %
                    (__bid.code, __bid.price, __bid.datetime, __bid.amount))
                return __message
            else:
                # self.account.order_queue=self.account.order_queue.append(__bid.to_df())
                return __message

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

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

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


        200 委托成功,完全交易
        203 委托成功,未完全成功
        300 刚创建订单的时候
        400 已撤单
        500 服务器撤单/每日结算
        """
        return self.account.order_queue[self.account.order_queue['order_id'] ==
                                        order_id_]['status']

    def QA_backtest_status(self):
        return vars(self)

    def QA_backtest_sell_all(self):
        __hold_list = pd.DataFrame(
            self.account.hold[1::], columns=self.account.hold[0]).set_index(
                'code', drop=False)['amount'].groupby('code').sum()

        for item in self.strategy_stock_list:
            try:
                if __hold_list[item] > 0:
                    self.QA_backtest_send_order(self, item, __hold_list[item],
                                                -1, {'bid_model': 'C'})

            except:
                pass

    @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.__sync_order_LM(__backtest_cls, 'init_')  # 初始化事件

            if __backtest_cls.backtest_type in ['day', 'd', 'index_day']:

                func(*arg, **kwargs)  # 发委托单
                __backtest_cls.__sell_from_order_queue(__backtest_cls)
            elif __backtest_cls.backtest_type in [
                    '1min', '5min', '15min', '30min', '60min', 'index_1min',
                    'index_5min', 'index_15min', 'index_30min', 'index_60min'
            ]:
                if __backtest_cls.backtest_type in ['1min', 'index_1min']:
                    type_ = '1min'
                elif __backtest_cls.backtest_type in ['5min', 'index_5min']:
                    type_ = '5min'
                elif __backtest_cls.backtest_type in ['15min', 'index_15min']:
                    type_ = '15min'
                elif __backtest_cls.backtest_type in ['30min', 'index_30min']:
                    type_ = '30min'
                elif __backtest_cls.backtest_type in ['60min', 'index_60min']:
                    type_ = '60min'
                daily_min = QA_util_make_min_index(__backtest_cls.today,
                                                   type_)  # 创造分钟线index
                # print(daily_min)
                for min_index in daily_min:
                    __backtest_cls.now = min_index
                    QA_util_log_info(
                        '=================Min hold list====================')
                    QA_util_log_info('in the begining of %s' % str(min_index))
                    QA_util_log_info(
                        tabulate(__backtest_cls.account.message['body']
                                 ['account']['hold']))
                    func(*arg, **kwargs)  # 发委托单
                    __backtest_cls.__sell_from_order_queue(__backtest_cls)
                    if __backtest_cls.backtest_type in [
                            'index_1min', 'index_5min', 'index_15min'
                    ]:
                        __backtest_cls.__sync_order_LM(__backtest_cls, 't_0')
            __backtest_cls.__sync_order_LM(__backtest_cls,
                                           'daily_settle')  # 每日结算

        # 最后一天
        __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):
        def __before_backtest(__backtest_cls, *arg, **kwargs):
            func(*arg, **kwargs)
            __backtest_cls.__QA_backtest_start(__backtest_cls)

        return __before_backtest(__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)
Esempio n. 14
0
    def __QA_backtest_prepare(self):
        """
        这是模型内部的 初始化,主要是初始化一些账户和市场资产
        写成了私有函数
        @yutiansut
        2017/7/20
        """

        self.strategy_stock_list = np.unique(
            self.strategy_stock_list).tolist()  # 保证不会重复
        if len(str(self.strategy_start_date)) == 10:
            self.strategy_start_time = str(
                self.strategy_start_date) + ' 15:00:00'
        elif len(str(self.strategy_start_date)) == 19:
            self.strategy_start_time = str(self.strategy_start_date)
            self.strategy_start_date = str(self.strategy_start_date)[0:10]
        else:
            self.__QA_backtest_log_info(self, 'Wrong start date format')

        if len(str(self.strategy_end_date)) == 10:
            self.strategy_end_time = str(self.strategy_end_date) + ' 15:00:00'
        elif len(str(self.strategy_end_date)) == 19:
            self.strategy_end_time = str(self.strategy_end_date)
            self.strategy_end_date = str(self.strategy_end_date)[0:10]
        else:
            self.__QA_backtest_log_info(self, 'Wrong end date format')
        # 重新初始账户资产
        self.market = QA_Market(self.commission_fee_coeff)
        self.setting.QA_setting_init()
        self.account.init()
        self.account_d_value.append(self.account.init_assest)
        self.start_real_date = QA_util_get_real_date(self.strategy_start_date,
                                                     self.trade_list, 1)
        self.start_real_time = str(
            self.start_real_date) + ' ' + self.strategy_start_time.split(
                ' ')[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.end_real_time = str(self.end_real_date) + \
            ' ' + self.strategy_end_time.split(' ')[1]
        # 重新初始化账户的cookie
        self.account.account_cookie = str(random.random())
        # 初始化股票池的市场数据
        if self.benchmark_type in ['I', 'index']:
            self.benchmark_data = QA_fetch_index_day_adv(
                self.benchmark_code, self.trade_list[self.start_real_id - 1],
                self.end_real_date)
        elif self.benchmark_type in ['S', 'stock']:
            self.benchmark_data = QA_fetch_stock_day_adv(
                self.benchmark_code, self.trade_list[self.start_real_id - 1],
                self.end_real_date)
        if self.backtest_type in ['day', 'd', '0x00']:
            self.market_data = QA_fetch_stocklist_day_adv(
                self.strategy_stock_list,
                self.trade_list[self.start_real_id -
                                int(self.strategy_gap + 1)],
                self.trade_list[self.end_real_id]).to_qfq()

        elif self.backtest_type in ['1min', '5min', '15min', '30min', '60min']:
            self.market_data = QA_fetch_stocklist_min_adv(
                self.strategy_stock_list,
                QA_util_time_gap(self.start_real_time, self.strategy_gap + 1,
                                 '<', self.backtest_type),
                QA_util_time_gap(self.end_real_time, 1, '>',
                                 self.backtest_type),
                self.backtest_type).to_qfq()

        elif self.backtest_type in ['index_day']:
            self.market_data = QA_fetch_index_day_adv(
                self.strategy_stock_list,
                self.trade_list[self.start_real_id -
                                int(self.strategy_gap + 1)],
                self.end_real_date)

        elif self.backtest_type in [
                'index_1min', 'index_5min', 'index_15min', 'index_30min',
                'index_60min'
        ]:
            self.market_data = QA_fetch_index_min_adv(
                self.strategy_stock_list,
                QA_util_time_gap(self.start_real_time, self.strategy_gap + 1,
                                 '<',
                                 self.backtest_type.split('_')[1]),
                QA_util_time_gap(self.end_real_time, 1, '>',
                                 self.backtest_type.split('_')[1]),
                self.backtest_type.split('_')[1])
        self.market_data_dict = dict(
            zip(list(self.market_data.code), self.market_data.splits()))
        self.market_data_hashable = self.market_data.dicts
        self.dirs = '.{}QUANTAXIS_RESULT{}{}{}{}{}'.format(
            os.sep, os.sep, self.topic_name, os.sep, self.stratey_version,
            os.sep)
        os.makedirs(self.dirs, exist_ok=True)
Esempio n. 15
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 = []
    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)
Esempio n. 16
0
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)
Esempio n. 17
0
class QA_Backtest_with_class():
    '因为是装饰器调用的 所以变量要写在外面 类装饰器不会调用__init__'
    backtest_type = 'day'
    account = QA_Account()
    market = QA_Market()
    bid = QA_QAMarket_bid()
    order = QA_QAMarket_bid_list()
    setting = QA_Setting()
    clients = setting.client
    user = setting.QA_setting_user_name
    market_data = []
    now = None
    today = None
    last_time = None
    strategy_stock_list = []
    trade_list = []
    start_real_id = 0
    end_real_id = 0
    temp = {}
    commission_fee_coeff = 0.0015
    strategy_start_date = ''
    strategy_start_time = ''
    strategy_end_date = ''
    strategy_end_time = ''
    benchmark_type = 'index'
    account_d_value = []
    account_d_key = []
    market_data_dict = {}
    backtest_print_log = True
    if_save_to_mongo = True
    if_save_to_csv = True
    outside_data = []
    outside_data_dict = []
    outside_data_hashable = {}
    topic_name = 'EXAMPLE'
    stratey_version = 'V1'
    absoult_path = sys.path[0]

    def __init__(self):

        self.backtest_type = 'day'
        self.account = QA_Account()
        self.market = QA_Market()
        self.order = QA_QAMarket_bid_list()
        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 = None
        self.last_time = None
        self.strategy_start_date = ''
        self.strategy_start_time = ''
        self.strategy_end_date = ''
        self.strategy_end_time = ''
        self.today = None
        self.strategy_stock_list = []
        self.trade_list = []
        self.start_real_id = 0
        self.end_real_id = 0
        self.temp = {}
        self.commission_fee_coeff = 0.0015
        self.account_d_value = []
        self.account_d_key = []
        self.benchmark_type = 'index'
        self.market_data_dict = {}
        self.backtest_print_log = True  # 打印
        self.if_save_to_mongo = True
        self.if_save_to_csv = True
        self.stratey_version = 'V1'
        self.topic_name = 'EXAMPLE'
        self.outside_data = []
        self.outside_data_dict = []
        self.outside_data_hashable = {}
        self.absoult_path = sys.path[0]
        self.dirs = '{}{}QUANTAXIS_RESULT{}{}{}{}{}'.format(
            self.absoult_path, os.sep, os.sep, self.topic_name, os.sep,
            self.stratey_version, os.sep)

    def __QA_backtest_init(self):
        self.__init__()
        """既然是被当做装饰器使用,就需要把变量设置放在装饰函数的前面,把函数放在装饰函数的后面"""
        # 设置回测的开始结束时间
        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 = trade_date_sse
        self.benchmark_code = '000300'
        """
        这里会涉及一个区间的问题,开始时间是要向后推,而结束时间是要向前推,1代表向后推,-1代表向前推
        """

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

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

        self.strategy_stock_list = np.unique(
            self.strategy_stock_list).tolist()  # 保证不会重复
        if len(str(self.strategy_start_date)) == 10:
            self.strategy_start_time = str(
                self.strategy_start_date) + ' 15:00:00'
        elif len(str(self.strategy_start_date)) == 19:
            self.strategy_start_time = str(self.strategy_start_date)
            self.strategy_start_date = str(self.strategy_start_date)[0:10]
        else:
            self.__QA_backtest_log_info('Wrong start date format')

        if len(str(self.strategy_end_date)) == 10:
            self.strategy_end_time = str(self.strategy_end_date) + ' 15:00:00'
        elif len(str(self.strategy_end_date)) == 19:
            self.strategy_end_time = str(self.strategy_end_date)
            self.strategy_end_date = str(self.strategy_end_date)[0:10]
        else:
            self.__QA_backtest_log_info('Wrong end date format')
        # 重新初始账户资产
        self.market = QA_Market(self.commission_fee_coeff)
        self.setting.QA_setting_init()
        self.account.init()
        self.account_d_value.append(self.account.init_assest)
        self.start_real_date = QA_util_get_real_date(self.strategy_start_date,
                                                     self.trade_list, 1)
        self.start_real_time = str(
            self.start_real_date) + ' ' + self.strategy_start_time.split(
                ' ')[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.end_real_time = str(self.end_real_date) + \
            ' ' + self.strategy_end_time.split(' ')[1]
        # 重新初始化账户的cookie
        self.account.account_cookie = str(random.random())
        # 初始化股票池的市场数据
        if self.benchmark_type in ['I', 'index']:
            self.benchmark_data = QA_fetch_index_day_adv(
                self.benchmark_code, self.trade_list[self.start_real_id - 1],
                self.end_real_date)
        elif self.benchmark_type in ['S', 'stock']:
            self.benchmark_data = QA_fetch_stock_day_adv(
                self.benchmark_code, self.trade_list[self.start_real_id - 1],
                self.end_real_date)
        if self.backtest_type in ['day', 'd', '0x00']:
            self.market_data = QA_fetch_stocklist_day_adv(
                self.strategy_stock_list,
                self.trade_list[self.start_real_id -
                                int(self.strategy_gap + 1)],
                self.trade_list[self.end_real_id]).to_qfq()

        elif self.backtest_type in ['1min', '5min', '15min', '30min', '60min']:
            self.market_data = QA_fetch_stocklist_min_adv(
                self.strategy_stock_list,
                QA_util_time_gap(self.start_real_time, self.strategy_gap + 1,
                                 '<', self.backtest_type),
                QA_util_time_gap(self.end_real_time, 1, '>',
                                 self.backtest_type),
                self.backtest_type).to_qfq()

        elif self.backtest_type in ['index_day']:
            self.market_data = QA_fetch_index_day_adv(
                self.strategy_stock_list,
                self.trade_list[self.start_real_id -
                                int(self.strategy_gap + 1)],
                self.end_real_date)

        elif self.backtest_type in [
                'index_1min', 'index_5min', 'index_15min', 'index_30min',
                'index_60min'
        ]:
            self.market_data = QA_fetch_index_min_adv(
                self.strategy_stock_list,
                QA_util_time_gap(self.start_real_time, self.strategy_gap + 1,
                                 '<',
                                 self.backtest_type.split('_')[1]),
                QA_util_time_gap(self.end_real_time, 1, '>',
                                 self.backtest_type.split('_')[1]),
                self.backtest_type.split('_')[1])
        self.market_data_dict = dict(
            zip(list(self.market_data.code), self.market_data.splits()))
        self.market_data_hashable = self.market_data.dicts
        self.dirs = '{}{}QUANTAXIS_RESULT{}{}{}{}{}'.format(
            self.absoult_path, os.sep, os.sep, self.topic_name, os.sep,
            self.stratey_version, os.sep)
        os.makedirs(self.dirs, exist_ok=True)
        self.lastest_price = {}
        try:
            self.outside_data_dict = dict(
                zip(list(self.outside_data.code), self.outside_data.splits()))
            self.outside_data_hashable = self.outside_data.dicts
        except:
            pass

    def __QA_backtest_log_info(self, log):
        if self.backtest_print_log:
            return QA_util_log_info(log)
        else:
            pass

    def __QA_backtest_before_backtest(self, *args, **kwargs):
        """
        这个是回测流程开始的入口
        """
        self.__QA_backtest_log_info(
            'QUANTAXIS Backtest Engine Initial Successfully')
        self.__QA_backtest_log_info('Basical Info: \n' + tabulate(
            [[str(__version__), str(self.strategy_name)]],
            headers=('Version', 'Strategy_name')))
        self.__QA_backtest_log_info('BACKTEST Cookie_ID is:  ' +
                                    str(self.account.account_cookie))
        self.__QA_backtest_log_info('Stock_List: \n' +
                                    tabulate([self.strategy_stock_list]))

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

    def __save_strategy_files(self):

        file_name = '{}backtest_{}.py'.format(self.dirs,
                                              self.account.account_cookie)

        with open(sys.argv[0], 'rb') as p:
            data = p.read()

            collection = self.setting.client.quantaxis.strategy

            collection.insert({
                'cookie': self.account.account_cookie,
                'name': self.strategy_name,
                'topic': self.topic_name,
                'version': self.stratey_version,
                'user': self.user,
                'datetime': datetime.datetime.now(),
                'content': data.decode('utf-8'),
                'dirs': self.dirs,
                'absoultpath': self.absoult_path
            })
            with open(file_name, 'wb') as f:

                f.write(data)

    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 _make_slice(self):

        QA_Setting.client.quantaxis.slice.insert({
            'cookie': self.account.account_cookie,
            'account_message': self.__messages,
            'account_d_value': self.account_d_value,
            'account_d_key': self.account_d_key,
            'now': self.now,
            'today': self.today,
            'running_date': self.running_date,
            'strategy_stock_list': self.strategy_stock_list,
            'dirs': self.dirs,
        })

    def _end_of_trading(self, *arg, **kwargs):
        # 在回测的最后一天,平掉所有仓位(回测的最后一天是不买入的)
        # 回测最后一天的交易处理
        # self._make_slice(self)
        if self.backtest_type in ['day']:
            self.now = str(self.end_real_date)
            self.today = str(self.end_real_date)
        elif self.backtest_type in ['1min', '5min', '15min', '30min', '60min']:
            self.now = str(self.end_real_date) + ' 15:00:00'
            self.today = str(self.end_real_date)
        elif self.backtest_type in ['index_day']:
            self.now = str(self.end_real_date)
            self.today = str(self.end_real_date)
        elif self.backtest_type in [
                'index_1min', 'index_5min', 'index_15min', 'index_30min',
                'index_60min'
        ]:
            self.now = str(self.end_real_date) + ' 15:00:00'
            self.today = str(self.end_real_date)

        self.today = self.end_real_date
        self.sell_all()
        self._deal_from_order_queue()
        self.__sync_order_LM('daily_settle')  # 每日结算

    def _deal_from_order_queue(self):

        # 每个bar结束的时候,批量交易
        __result = []
        self.order.__init__()
        if len(self.account.order_queue) >= 1:
            __bid_list = self.order.from_dataframe(
                self.account.order_queue.query('status!=200').query(
                    'status!=500').query('status!=400'))

            for item in __bid_list:
                # 在发单的时候要改变交易日期
                item.date = self.today
                item.datetime = self.now

                __bid, __market = self.__wrap_bid(self, item)

                __message = self.__send_bid(__bid, __market)

                if isinstance(__message, dict):
                    if __message['header']['status'] in ['200', 200]:
                        self.__sync_order_LM('trade', __bid,
                                             __message['header']['order_id'],
                                             __message['header']['trade_id'],
                                             __message)
                    else:
                        self.__sync_order_LM('wait')
        else:
            self.__QA_backtest_log_info(
                'FROM BACKTEST: Order Queue is empty at %s!' % self.now)
            pass

    def __sync_order_LM(self,
                        event_,
                        order_=None,
                        order_id_=None,
                        trade_id_=None,
                        market_message_=None):
        """
        订单事件: 生命周期管理 Order-Lifecycle-Management
        status1xx 订单待生成
        status3xx 初始化订单  临时扣除资产(可用现金/可卖股份)
        status3xx 订单存活(等待交易)
        status2xx 订单完全交易/未完全交易
        status4xx 主动撤单
        status500 订单死亡(每日结算) 恢复临时资产    
        =======
        1. 更新持仓
        2. 更新现金
        """
        if event_ is 'init_':

            self.account.cash_available = self.account.cash[-1]
            self.account.sell_available = pd.DataFrame(
                self.account.hold[1::],
                columns=self.account.hold[0]).set_index(
                    'code', drop=False)['amount'].groupby('code').sum()

        elif event_ is 'create_order':

            if order_ is not None:
                if order_.towards is 1:
                    # 买入
                    if self.account.cash_available - order_.amount * order_.price > 0:
                        self.account.cash_available -= order_.amount * order_.price
                        order_.status = 300  # 修改订单状态

                        self.account.order_queue = self.account.order_queue.append(
                            order_.to_df())
                    else:
                        self.__QA_backtest_log_info(
                            'FROM ENGINE: NOT ENOUGH MONEY:CASH  %s Order %s' %
                            (self.account.cash_available,
                             order_.amount * order_.price))
                elif order_.towards is -1:

                    if self.QA_backtest_sell_available(
                            order_.code) - order_.amount >= 0:
                        self.account.sell_available[
                            order_.code] -= order_.amount
                        self.account.order_queue = self.account.order_queue.append(
                            order_.to_df())

            else:
                self.__QA_backtest_log_info('Order Event Warning:%s in %s' %
                                            (event_, str(self.now)))

        elif event_ in ['wait', 'live']:
            # 订单存活 不会导致任何状态改变
            pass
        elif event_ in ['cancel_order']:  # 订单事件:主动撤单
            # try:
            assert isinstance(order_id_, str)
            self.account.order_queue.loc[
                self.account.order_queue['order_id'] == order_id_,
                'status'] = 400  # 注销事件
            if order_.towards is 1:
                # 多单 撤单  现金增加
                self.account.cash_available += self.account.order_queue.query(
                    'order_id=="order_id_"'
                )['amount'] * self.account.order_queue.query(
                    'order_id=="order_id_"')['price']

            elif order_.towards is -1:
                # 空单撤单 可卖数量增加
                self.account.sell_available[
                    order_.code] += self.account.order_queue.query(
                        'order_id=="order_id_"')['price']
        elif event_ in ['daily_settle']:  # 每日结算/全撤/把成交的买入/卖出单标记为500 同时结转

            # 买入
            """
            每日结算流程
            - 同步实际的现金和仓位
            - 清空留仓单/未成功的订单
            """

            self.account.cash_available = self.account.cash[-1]
            self.account.sell_available = self.QA_backtest_hold(
            )['amount'].groupby('code').sum()

            self.account.order_queue = pd.DataFrame()

            self.account_d_key.append(self.today)

            if len(self.account.hold) > 1:
                self.account_d_value.append(self.account.cash[-1] + sum([
                    self.lastest_price[self.account.hold[i][1]] *
                    float(self.account.hold[i][3])
                    for i in range(1, len(self.account.hold))
                ]))
            else:
                self.account_d_value.append(self.account.cash[-1])
        elif event_ in ['t_0']:
            """
            T+0交易事件

            同步t+0的账户状态 /允许卖出
            """
            self.account.cash_available = self.account.cash[-1]
            self.account.sell_available = self.QA_backtest_hold(
            )['amount'].groupby('code').sum()

        elif event_ in ['trade']:
            # try:
            assert isinstance(order_, QA_QAMarket_bid)
            assert isinstance(order_id_, str)
            assert isinstance(trade_id_, str)
            assert isinstance(market_message_, dict)
            if order_.towards is 1:
                # 买入
                # 减少现金
                order_.trade_id = trade_id_
                order_.transact_time = self.now
                order_.amount -= market_message_['body']['bid']['amount']

                if order_.amount == 0:  # 完全交易
                    # 注销(成功交易)['买入单不能立即结转']
                    self.account.order_queue.loc[
                        self.account.order_queue['order_id'] == order_id_,
                        'status'] = 200

                elif order_.amount > 0:
                    # 注销(成功交易)
                    self.account.order_queue.loc[
                        self.account.order_queue['order_id'] == order_id_,
                        'status'] = 203
                    self.account.order_queue.query('order_id=="order_id_"')[
                        'amount'] -= market_message_['body']['bid']['amount']
            elif order_.towards is -1:
                # self.account.sell_available[order_.code] -= market_message_[
                #    'body']['bid']['amount']
                # 当日卖出的股票 可以继续买入/ 可用资金增加(要减去手续费)
                self.account.cash_available += market_message_['body']['bid'][
                    'amount'] * market_message_['body']['bid'][
                        'price'] - market_message_['body']['fee']['commission']
                order_.trade_id = trade_id_
                order_.transact_time = self.now
                order_.amount -= market_message_['body']['bid']['amount']
                if order_.amount == 0:
                    # 注销(成功交易)
                    self.account.order_queue.loc[
                        self.account.order_queue['order_id'] == order_id_,
                        'status'] = 200
                else:
                    # 注销(成功交易)
                    self.account.order_queue.loc[
                        self.account.order_queue['order_id'] == order_id_,
                        'status'] = 203
                    self.account.order_queue[
                        self.account.order_queue['order_id'] ==
                        order_id_]['amount'] -= market_message_['body']['bid'][
                            'amount']
        else:
            self.__QA_backtest_log_info(
                'EventEngine Warning: Unknown type of order event in  %s' %
                str(self.now))

    def __send_bid(self, __bid, __market=None):
        __message = self.market.receive_bid(__bid, __market)
        if __bid.towards == 1:
            # 扣费
            # 以下这个订单时的bar的open扣费
            # 先扔进去买入,再通过返回的值来判定是否成功
            if __message['header']['status'] == 200 and __message['body'][
                    'bid']['amount'] > 0:
                # 这个判断是为了 如果买入资金不充足,所以买入报了一个0量单的情况
                # 如果买入量>0, 才判断为成功交易
                self.__QA_backtest_log_info(
                    'BUY %s Price %s Date %s Amount %s' %
                    (__bid.code, __bid.price, __bid.datetime, __bid.amount))
                self.__messages = self.account.QA_account_receive_deal(
                    __message)
                return __message
            else:

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

        elif __bid.towards == -1:
            # 如果是卖出操作 检查是否有持仓
            # 股票中不允许有卖空操作
            # 检查持仓面板
            if __message['header']['status'] == 200:
                self.__messages = self.account.QA_account_receive_deal(
                    __message)
                self.__QA_backtest_log_info(
                    'SELL %s Price %s Date %s  Amount %s' %
                    (__bid.code, __bid.price, __bid.datetime, __bid.amount))
                return __message
            else:
                # self.account.order_queue=self.account.order_queue.append(__bid.to_df())
                return __message

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

    def __wrap_bid(self, __bid, __order=None):
        __market_data_for_backtest = self.find_bar(__bid.code, __bid.datetime)
        if __market_data_for_backtest is not None:

            if __market_data_for_backtest[
                    'open'] is not None and __order is not None:
                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'
                ]:
                    # 2017-09-18 修改  市价单以当前bar开盘价下单
                    __bid.price = float(__market_data_for_backtest['open'])
                elif __order['bid_model'] in [
                        'strict', 'Strict', 's', 'S', '2', 2
                ]:
                    __bid.price = float(__market_data_for_backtest['high']
                                        ) if __bid.towards == 1 else float(
                                            __market_data_for_backtest['low'])
                elif __order['bid_model'] in [
                        'close', 'close_price', 'c', 'C', '3', 3
                ]:
                    __bid.price = float(__market_data_for_backtest['close'])

                __bid.price = float('%.2f' % __bid.price)
                return __bid, __market_data_for_backtest
            else:
                return __bid, __market_data_for_backtest

        else:
            self.__QA_backtest_log_info(
                'BACKTEST ENGINE ERROR=== CODE %s TIME %s NO MARKET DATA!' %
                (__bid.code, __bid.datetime))
            return __bid, None

    def __end_of_backtest(self, *arg, **kwargs):
        # 开始分析
        # 对于account.detail做一定的整理
        self.account.detail = pd.DataFrame(
            self.account.detail,
            columns=[
                'date', 'code', 'price', 'amounts', 'order_id', 'trade_id',
                'sell_price', 'sell_order_id', 'sell_trade_id', 'sell_date',
                'left_amount', 'commission'
            ])

        def __mean(list_):
            if len(list_) > 0:
                return mean(list_)
            else:
                return 'No Data'

        self.account.detail['sell_average'] = self.account.detail[
            'sell_price'].apply(lambda x: __mean(x))

        try:
            self.account.detail['pnl_price'] = self.account.detail['sell_average'] - \
                self.account.detail['price']

            self.account.detail['pnl'] = self.account.detail['pnl_price'] * (
                self.account.detail['amounts'] - self.account.
                detail['left_amount']) - self.account.detail['commission']

            self.account.detail['pnl_precentage'] = self.account.detail['pnl_price'] / \
                self.account.detail['price']
        except:
            pass
        self.account.detail = self.account.detail.drop(
            ['order_id', 'trade_id', 'sell_order_id', 'sell_trade_id'], axis=1)
        self.__QA_backtest_log_info('start analysis====\n' +
                                    str(self.strategy_stock_list))
        self.__QA_backtest_log_info('=' * 10 + 'Trade History' + '=' * 10)
        self.__QA_backtest_log_info(
            '\n' +
            tabulate(self.account.history,
                     headers=('date', 'code', 'price', 'towards', 'amounts',
                              'order_id', 'trade_id', 'commission')))
        self.__QA_backtest_log_info('\n' + tabulate(
            self.account.detail, headers=(self.account.detail.columns)))
        __exist_time = int(self.end_real_id) - int(self.start_real_id) + 1
        if len(self.__messages) > 1:
            performace = QA_backtest_analysis_backtest(
                self.setting.client, self.strategy_stock_list,
                self.account_d_value, self.account_d_key, self.__messages,
                self.trade_list[self.start_real_id:self.end_real_id + 1],
                self.benchmark_data.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()
            }

            if self.if_save_to_mongo:
                QA_SU_save_backtest_message(_backtest_mes, self.setting.client)
                QA_SU_save_account_message(self.__messages,
                                           self.setting.client)
            if self.if_save_to_csv:
                QA_SU_save_account_to_csv(self.__messages, self.dirs)

                self.account.detail.to_csv('{}backtest-pnl-{}.csv'.format(
                    self.dirs, str(self.account.account_cookie)))
                self.__save_strategy_files()

    def __check_state(self, bid_price, bid_amount):
        pass

    def QA_Backtest_before_init(self):
        return self.__QA_backtest_init()

    def QA_Backtest_after_init(self):
        return self.__QA_backtest_prepare()

    @lru_cache()
    def find_bar(self, code, time):
        if isinstance(time, str):
            if len(time) == 10:
                try:
                    try:
                        return self.market_data_hashable[(
                            datetime.datetime.strptime(time,
                                                       '%Y-%m-%d'), code)]
                    except:
                        return self.outside_data_hashable[(
                            datetime.datetime.strptime(time,
                                                       '%Y-%m-%d'), code)]
                except:
                    return None
            elif len(time) == 19:
                try:
                    try:
                        return self.market_data_hashable[(
                            datetime.datetime.strptime(time,
                                                       '%Y-%m-%d %H:%M:%S'),
                            code)]
                    except:
                        return self.outside_data_hashable[(
                            datetime.datetime.strptime(time,
                                                       '%Y-%m-%d %H:%M:%S'),
                            code)]
                except:
                    return None
        else:
            try:
                try:
                    return self.market_data_hashable[(time, code)]
                except:
                    return self.outside_data_hashable[(time, code)]
            except:
                return None

    @lru_cache()
    def get_market_data(self, code, date, gap_=None, type_='lt'):
        '这个函数封装了关于获取的方式 用GAP的模式'
        gap_ = self.strategy_gap if gap_ is None else gap_
        try:
            try:
                return self.market_data_dict[code].select_time_with_gap(
                    date, gap_, type_)
            except:
                return self.outside_data_dict[code].select_time_with_gap(
                    date, gap_, type_)
        except:
            return None

    @lru_cache()
    def get_market_data_panel(self, date=None, type_='lt'):
        try:
            if date is not None:
                try:
                    return self.market_data.select_time_with_gap(
                        date, 1, type_)
                except:
                    return self.outside_data.select_time_with_gap(
                        date, 1, type_)
            else:
                try:
                    return self.market_data.select_time_with_gap(
                        self.now, 1, type_)
                except:
                    return self.outside_data.select_time_with_gap(
                        self.now, 1, type_)
        except Exception as e:
            raise e

    @lru_cache()
    def get_market_data_bar(self, code, time, if_trade=True):
        '这个函数封装了关于获取的方式'
        try:
            try:
                return self.market_data_dict[code].get_bar(
                    code, time, if_trade)
            except:
                return self.outside_data_dict[code].get_bar(
                    code, time, if_trade)
        except:
            return None

    def get_block(self, block_list):
        block_ = QA_fetch_stock_block_adv()
        _data = []

        try:
            for item in block_list:

                _data.extend(block_.get_block(item).code)
            return np.unique(_data).tolist()
        except Exception as e:
            raise e

    #@lru_cache()
    def QA_backtest_sell_available(self, __code):
        try:
            return self.account.sell_available[__code]
        except:
            return 0

# @lru_cache()

    def QA_backtest_hold(self):
        return pd.DataFrame(self.account.hold[1::],
                            columns=self.account.hold[0]).set_index('code',
                                                                    drop=False)

    def hold_amount(self, __code):
        try:
            return pd.DataFrame(
                self.account.hold[1::],
                columns=self.account.hold[0]).set_index(
                    'code', drop=False)['amount'].groupby('code').sum()[__code]
        except:
            return 0

    def hold_price(self, __code):
        try:
            return self.QA_backtest_hold(self)['price'].groupby(
                'code').mean()[__code]
        except:
            return None

    @lru_cache()
    def get_OHLCV(self, __data):
        '快速返回 OHLCV格式'
        return (__data.open, __data.high, __data.low, __data.close, __data.vol)

    def send_order(self, code, amount, towards, order_type):
        """
        2017/8/4
        委托函数
        在外部封装的一个报价接口,尽量满足和实盘一样的模式

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


        功能
        =============
        1. 封装一个bid类(分配地址)
        2. 检查账户/临时扣费
        3. 检查市场(wrap)
        4. 发送到_send_bid方法
        """

        # 必须是100股的倍数
        # 封装bid

        _bid = QA_QAMarket_bid()  # init
        (_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,
                          str(self.now), self.running_date, amount, towards)

        # 2017-09-21 修改: 只有股票的交易才需要控制amount的最小交易单位
        if self.backtest_type in ['day']:
            _bid.type = '0x01'
            _bid.amount = int(_bid.amount / 100) * 100
        elif self.backtest_type in ['1min', '5min', '15min', '30min', '60min']:
            _bid.type = '0x02'
            _bid.amount = int(_bid.amount / 100) * 100
        elif self.backtest_type in ['index_day']:
            _bid.type = '0x03'
            _bid.amount = int(_bid.amount)
        elif self.backtest_type in [
                'index_1min', 'index_5min', 'index_15min', 'index_30min',
                'index_60min'
        ]:
            _bid.type = '0x04'
            _bid.amount = int(_bid.amount)
        # 检查账户/临时扣费

        _bid, _market = self.__wrap_bid(_bid, order_type)

        if _bid is not None and _market is not None and _bid.amount > 0:
            print(
                'GET the Order Code %s Amount %s Price %s Towards %s Time %s' %
                (_bid.code, _bid.amount, _bid.price, _bid.towards,
                 _bid.datetime))
            self.__sync_order_LM('create_order', order_=_bid)

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

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


        200 委托成功,完全交易
        203 委托成功,未完全成功
        300 刚创建订单的时候
        400 已撤单
        500 服务器撤单/每日结算
        """
        return self.account.order_queue[self.account.order_queue['order_id'] ==
                                        order_id_]['status']

    @lru_cache()
    def status(self):
        return vars(self)

    @lru_cache()
    def sell_all(self):
        __hold_list = pd.DataFrame(
            self.account.hold[1::], columns=self.account.hold[0]).set_index(
                'code', drop=False)['amount'].groupby('code').sum()

        for item in self.strategy_stock_list:
            try:
                if __hold_list[item] > 0:
                    self.send_order(self, item, __hold_list[item], -1,
                                    {'bid_model': 'C'})

            except:
                pass

    def _load_strategy(self, *arg, **kwargs):
        '策略加载函数'

        # 首先判断是否能满足回测的要求`
        __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)):
            self.running_date = self.trade_list[i]
            self.__QA_backtest_log_info(
                '=================daily hold list====================')
            self.__QA_backtest_log_info('in the begining of ' +
                                        self.running_date)
            self.__QA_backtest_log_info(
                tabulate(self.account.message['body']['account']['hold']))

            if self.now is not None:
                self.last_time = self.now

            self.now = self.running_date
            self.today = self.running_date

            # 交易前同步持仓状态
            self.__sync_order_LM(self, 'init_')  # 初始化事件

            if self.backtest_type in ['day', 'd', 'index_day']:

                _temp = self.market_data.select_time(
                    self.today,
                    self.today).data.set_index('code').close.to_dict()
                for key in _temp.keys():
                    self.lastest_price[key] = _temp[key]
                self.strategy(*arg, **kwargs)  # 发委托单
                self._deal_from_order_queue()
            elif self.backtest_type in [
                    '1min', '5min', '15min', '30min', '60min', 'index_1min',
                    'index_5min', 'index_15min', 'index_30min', 'index_60min'
            ]:
                if self.backtest_type in ['1min', 'index_1min']:
                    type_ = '1min'
                elif self.backtest_type in ['5min', 'index_5min']:
                    type_ = '5min'
                elif self.backtest_type in ['15min', 'index_15min']:
                    type_ = '15min'
                elif self.backtest_type in ['30min', 'index_30min']:
                    type_ = '30min'
                elif self.backtest_type in ['60min', 'index_60min']:
                    type_ = '60min'
                daily_min = QA_util_make_min_index(self.today,
                                                   type_)  # 创造分钟线index
                for min_index in daily_min:
                    self.now = min_index

                    self.__QA_backtest_log_info(
                        '=================Min hold list====================')
                    self.__QA_backtest_log_info('in the begining of %s' %
                                                str(min_index))
                    self.__QA_backtest_log_info(
                        tabulate(
                            self.account.message['body']['account']['hold']))

                    _temp = self.market_data.select_time(
                        self.now,
                        self.now).data.set_index('code').close.to_dict()
                    for key in _temp.keys():
                        self.lastest_price[key] = _temp[key]

                    self.strategy(*arg, **kwargs)  # 发委托单

                    self._deal_from_order_queue()
                    if self.backtest_type in [
                            'index_1min', 'index_5min', 'index_15min'
                    ]:
                        self.__sync_order_LM('t_0')
            self.__sync_order_LM('daily_settle')  # 每日结算

        # 最后一天
        self._end_of_trading()

    def _backtest_init(self):

        self.__QA_backtest_init()
        self.backtest_init()
        self.__QA_backtest_prepare()

    def _before_backtest(self):

        self.__QA_backtest_before_backtest()
        self.before_backtest()

    def _end_backtest(self):
        self.end_backtest()
        self.__end_of_backtest()

    def run(self):
        self._backtest_init()
        self._before_backtest()
        self._load_strategy()
        self._end_backtest()

    # 暂时不确定要不要用

    def strategy(self):
        pass

    def backtest_init(self):
        pass

    def before_backtest(self):
        pass

    def end_backtest(self):
        pass
Esempio n. 18
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}
Esempio n. 19
0
class QA_Backtest():
    '最终目的还是实现一个通用的回测类'
    backtest_type = 'day'
    account = QA_Account()
    market = QA_Market()
    bid = QA_QAMarket_bid()
    order = QA_QAMarket_bid_list()
    setting = QA_Setting()
    clients = setting.client
    user = setting.QA_setting_user_name
    market_data = []
    now = ''
    today = ''
    strategy_stock_list = []
    trade_list = []
    start_real_id = 0
    end_real_id = 0
    temp = {}
    commission_fee_coeff = 0.0015

    def __init__(self):

        self.backtest_type = 'day'
        self.account = QA_Account()
        self.market = QA_Market()
        self.order = QA_QAMarket_bid_list()
        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 = ''
        self.strategy_stock_list = []
        self.trade_list = []
        self.start_real_id = 0
        self.end_real_id = 0
        self.temp = {}
        self.commission_fee_coeff = 0.0015

    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 = trade_date_sse
        self.benchmark_code = '000300'
        """
        这里会涉及一个区间的问题,开始时间是要向后推,而结束时间是要向前推,1代表向后推,-1代表向前推
        """

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

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

        # 重新初始账户资产
        self.market = QA_Market(self.commission_fee_coeff)
        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.benchmark_data = QA_fetch_index_day_adv(
            self.benchmark_code, self.start_real_date, self.end_real_date)
        if self.backtest_type in ['day', 'd', '0x00']:
            self.market_data = QA_fetch_stocklist_day_adv(
                self.strategy_stock_list, self.trade_list[self.start_real_id - int(
                    self.strategy_gap)], self.trade_list[self.end_real_id]).to_qfq()

        elif self.backtest_type in ['1min', '5min', '15min', '30min', '60min']:
            self.market_data = QA_fetch_stocklist_min_adv(
                self.strategy_stock_list, self.trade_list[
                    self.start_real_id - int(self.strategy_gap)],
                self.trade_list[self.end_real_id + 1], self.backtest_type).to_qfq()

        elif self.backtest_type in ['index_day']:
            self.market_data = QA_fetch_index_day_adv(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 ['index_1min', 'index_5min', 'index_15min', 'index_30min', 'index_60min']:
            self.market_data = QA_fetch_index_min_adv(
                self.strategy_stock_list, self.start_real_date, self.end_real_date, self.backtest_type.split('_')[1])

    def __QA_backtest_start(self, *args, **kwargs):
        """
        这个是回测流程开始的入口
        """
        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.__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):
        # 在回测的最后一天,平掉所有仓位(回测的最后一天是不买入的)
        # 回测最后一天的交易处理
        self.now = str(self.end_real_date) + ' 15:00:00'
        self.today = self.end_real_date
        self.QA_backtest_sell_all(self)
        self.__sell_from_order_queue(self)
        self.__sync_order_LM(self, 'daily_settle')  # 每日结算

    def __wrap_bid(self, __bid, __order=None):
        __market_data_for_backtest = self.market_data.get_bar(
            __bid.code, __bid.datetime)
        if __market_data_for_backtest.len() == 1:

            __O, __H, __L, __C, __V = self.QA_backtest_get_OHLCV(
                self, __market_data_for_backtest)
            if __O is not None and __order is not None:
                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']:
                    # 2017-09-18 修改  市价单以当前bar开盘价下单
                    __bid.price = float(__O[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, __market_data_for_backtest
            else:
                return __bid, __market_data_for_backtest

        else:
            QA_util_log_info('BACKTEST ENGINE ERROR=== CODE %s TIME %s NO MARKET DATA!' % (
                __bid.code, __bid.datetime))
            return __bid, 500

    def __end_of_backtest(self, *arg, **kwargs):
        # 开始分析

        # 对于account.detail做一定的整理
        self.account.detail = detail = pd.DataFrame(self.account.detail, columns=['date', 'code', 'price', 'amounts', 'order_id',
                                                                                  'trade_id', 'sell_price', 'sell_order_id',
                                                                                  'sell_trade_id', 'sell_date', 'left_amount',
                                                                                  'commission'])
        self.account.detail['sell_average'] = self.account.detail['sell_price'].apply(
            lambda x: mean(x))
        self.account.detail['pnl_persentage'] = self.account.detail['sell_average'] - \
            self.account.detail['price']

        self.account.detail['pnl'] = self.account.detail['pnl_persentage'] * (
            self.account.detail['amounts'] - self.account.detail['left_amount']) - self.account.detail['commission']
        self.account.detail = self.account.detail.drop(
            ['order_id', 'trade_id', 'sell_order_id', 'sell_trade_id'], axis=1)
        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=(self.account.detail.columns)))
        __exist_time = int(self.end_real_id) - int(self.start_real_id) + 1
        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.benchmark_data.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)

            self.account.detail.to_csv(
                'backtest-pnl--' + str(self.account.account_cookie) + '.csv')

    def QA_backtest_get_market_data(self, code, date, gap_=None):
        '这个函数封装了关于获取的方式 用GAP的模式'
        gap_ = self.strategy_gap if gap_ is None else gap_
        return self.market_data.select_code(code).select_time_with_gap(date, gap_, 'lte')

    def QA_backtest_get_market_data_bar(self, code, time):
        '这个函数封装了关于获取的方式'
        return self.market_data.get_bar(code, time)

    def QA_backtest_sell_available(self, __code):
        try:
            return self.account.sell_available[__code]
        except:
            return 0

    def QA_backtest_hold_amount(self, __code):
        try:
            return pd.DataFrame(self.account.hold[1::], columns=self.account.hold[0]).set_index(
                'code', drop=False)['amount'].groupby('code').sum()[__code]
        except:
            return 0

    def __sell_from_order_queue(self):

        # 每个bar结束的时候,批量交易
        __result = []
        self.order.__init__()
        if len(self.account.order_queue) >= 1:
            __bid_list = self.order.from_dataframe(self.account.order_queue.query(
                'status!=200').query('status!=500').query('status!=400'))

            for item in __bid_list:
                # 在发单的时候要改变交易日期
                item.date = self.today
                item.datetime = self.now

                __bid, __market = self.__wrap_bid(self, item)
                __message = self.__QA_backtest_send_bid(
                    self, __bid, __market.to_json()[0])
                if isinstance(__message, dict):
                    if __message['header']['status'] in ['200', 200]:
                        self.__sync_order_LM(
                            self, 'trade', __bid, __message['header']['order_id'], __message['header']['trade_id'], __message)
                    else:
                        self.__sync_order_LM(self, 'wait')
        else:
            QA_util_log_info(
                'FROM BACKTEST: Order Queue is empty at %s!' % self.now)
            pass

    def QA_backtest_get_OHLCV(self, __data):
        '快速返回 OHLCV格式'
        return (__data.open, __data.high, __data.low, __data.close, __data.vol)

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

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


        功能
        =============
        1. 封装一个bid类(分配地址)
        2. 检查账户/临时扣费
        3. 检查市场(wrap)
        4. 发送到_send_bid方法
        """

        # 必须是100股的倍数
        # 封装bid

        __bid = QA_QAMarket_bid()  # init
        (__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, str(
                                             self.now),
                                         self.running_date, __amount, __towards)

        # 2017-09-21 修改: 只有股票的交易才需要控制amount的最小交易单位
        if self.backtest_type in ['day']:
            __bid.type = '0x01'
            __bid.amount = int(__bid.amount / 100) * 100
        elif self.backtest_type in ['1min', '5min', '15min', '30min', '60min']:
            __bid.type = '0x02'
            __bid.amount = int(__bid.amount / 100) * 100
        elif self.backtest_type in ['index_day']:
            __bid.type = '0x03'
            __bid.amount = int(__bid.amount)
        elif self.backtest_type in ['index_1min', 'index_5min', 'index_15min', 'index_30min', 'index_60min']:
            __bid.type = '0x04'
            __bid.amount = int(__bid.amount)
        # 检查账户/临时扣费

        __bid, __market = self.__wrap_bid(self, __bid, __order)
        if __bid is not None and __market != 500:
            print('GET the Order Code %s Amount %s Price %s Towards %s Time %s' % (
                __bid.code, __bid.amount, __bid.price, __bid.towards, __bid.datetime))
            self.__sync_order_LM(self, 'create_order', order_=__bid)

    def __sync_order_LM(self, event_, order_=None, order_id_=None, trade_id_=None, market_message_=None):
        """
        订单事件: 生命周期管理 Order-Lifecycle-Management
        status1xx 订单待生成
        status3xx 初始化订单  临时扣除资产(可用现金/可卖股份)
        status3xx 订单存活(等待交易)
        status2xx 订单完全交易/未完全交易
        status4xx 主动撤单
        status500 订单死亡(每日结算) 恢复临时资产    
        =======
        1. 更新持仓
        2. 更新现金
        """
        if event_ is 'init_':

            self.account.cash_available = self.account.cash[-1]
            self.account.sell_available = pd.DataFrame(self.account.hold[1::], columns=self.account.hold[0]).set_index(
                'code', drop=False)['amount'].groupby('code').sum()

        elif event_ is 'create_order':

            if order_ is not None:
                if order_.towards is 1:
                    # 买入
                    if self.account.cash_available - order_.amount * order_.price > 0:
                        self.account.cash_available -= order_.amount * order_.price
                        order_.status = 300  # 修改订单状态

                        self.account.order_queue = self.account.order_queue.append(
                            order_.to_df())
                    else:
                        QA_util_log_info('FROM ENGINE: NOT ENOUGH MONEY:CASH  %s Order %s' % (
                            self.account.cash_available, order_.amount * order_.price))
                elif order_.towards is -1:

                    if self.QA_backtest_sell_available(self, order_.code) - order_.amount >= 0:
                        self.account.sell_available[order_.code] -= order_.amount
                        self.account.order_queue = self.account.order_queue.append(
                            order_.to_df())

            else:
                QA_util_log_info('Order Event Warning:%s in %s' %
                                 (event_, str(self.now)))

        elif event_ in ['wait', 'live']:
            # 订单存活 不会导致任何状态改变
            pass
        elif event_ in ['cancel_order']:  # 订单事件:主动撤单
            # try:
            assert isinstance(order_id_, str)
            self.account.order_queue.loc[self.account.order_queue['order_id']
                                         == order_id_, 'status'] = 400  # 注销事件
            if order_.towards is 1:
                # 多单 撤单  现金增加
                self.account.cash_available += self.account.order_queue.query('order_id=="order_id_"')[
                    'amount'] * self.account.order_queue.query('order_id=="order_id_"')['price']

            elif order_.towards is -1:
                # 空单撤单 可卖数量增加
                self.account.sell_available[order_.code] += self.account.order_queue.query(
                    'order_id=="order_id_"')['price']
        elif event_ in ['daily_settle']:  # 每日结算/全撤/把成交的买入/卖出单标记为500 同时结转

            # 买入
            """
            每日结算流程
            - 同步实际的现金和仓位
            - 清空留仓单/未成功的订单
            """

            self.account.cash_available = self.account.cash[-1]
            self.account.sell_available = pd.DataFrame(self.account.hold[1::], columns=self.account.hold[0]).set_index(
                'code', drop=False)['amount'].groupby('code').sum()

            self.account.order_queue = pd.DataFrame()
        elif event_ in ['t_0']:
            """
            T+0交易事件

            同步t+0的账户状态 /允许卖出
            """
            self.account.cash_available = self.account.cash[-1]
            self.account.sell_available = pd.DataFrame(self.account.hold[1::], columns=self.account.hold[0]).set_index(
                'code', drop=False)['amount'].groupby('code').sum()

        elif event_ in ['trade']:
            # try:
            assert isinstance(order_, QA_QAMarket_bid)
            assert isinstance(order_id_, str)
            assert isinstance(trade_id_, str)
            assert isinstance(market_message_, dict)
            if order_.towards is 1:
                # 买入
                # 减少现金
                order_.trade_id = trade_id_
                order_.transact_time = self.now
                order_.amount -= market_message_['body']['bid']['amount']

                if order_.amount == 0:  # 完全交易
                    # 注销(成功交易)['买入单不能立即结转']
                    self.account.order_queue.loc[self.account.order_queue['order_id']
                                                 == order_id_, 'status'] = 200

                elif order_.amount > 0:
                    # 注销(成功交易)
                    self.account.order_queue.loc[self.account.order_queue['order_id']
                                                 == order_id_, 'status'] = 203
                    self.account.order_queue.query('order_id=="order_id_"')[
                        'amount'] -= market_message_['body']['bid']['amount']
            elif order_.towards is -1:
                # self.account.sell_available[order_.code] -= market_message_[
                #    'body']['bid']['amount']
                # 当日卖出的股票 可以继续买入/ 可用资金增加(要减去手续费)
                self.account.cash_available += market_message_['body']['bid']['amount'] * market_message_[
                    'body']['bid']['price'] - market_message_['body']['fee']['commission']
                order_.trade_id = trade_id_
                order_.transact_time = self.now
                order_.amount -= market_message_['body']['bid']['amount']
                if order_.amount == 0:
                    # 注销(成功交易)
                    self.account.order_queue.loc[self.account.order_queue['order_id']
                                                 == order_id_, 'status'] = 200
                else:
                    # 注销(成功交易)
                    self.account.order_queue.loc[self.account.order_queue['order_id']
                                                 == order_id_, 'status'] = 203
                    self.account.order_queue[self.account.order_queue['order_id'] ==
                                             order_id_]['amount'] -= market_message_['body']['bid']['amount']
        else:
            QA_util_log_info(
                'EventEngine Warning: Unknown type of order event in  %s' % str(self.now))

    def __QA_backtest_send_bid(self, __bid, __market=None):
        __message = self.market.receive_bid(__bid, __market)
        if __bid.towards == 1:
            # 扣费
            # 以下这个订单时的bar的open扣费
            # 先扔进去买入,再通过返回的值来判定是否成功
            if __message['header']['status'] == 200 and __message['body']['bid']['amount'] > 0:
                # 这个判断是为了 如果买入资金不充足,所以买入报了一个0量单的情况
                # 如果买入量>0, 才判断为成功交易
                QA_util_log_info('BUY %s Price %s Date %s Amount %s' % (
                    __bid.code, __bid.price, __bid.datetime, __bid.amount))
                self.__messages = self.account.QA_account_receive_deal(
                    __message)
                return __message
            else:

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

        elif __bid.towards == -1:
            # 如果是卖出操作 检查是否有持仓
            # 股票中不允许有卖空操作
            # 检查持仓面板
            if __message['header']['status'] == 200:
                self.__messages = self.account.QA_account_receive_deal(
                    __message)
                QA_util_log_info('SELL %s Price %s Date %s  Amount %s' % (
                    __bid.code, __bid.price, __bid.datetime, __bid.amount))
                return __message
            else:
                # self.account.order_queue=self.account.order_queue.append(__bid.to_df())
                return __message

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

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

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


        200 委托成功,完全交易
        203 委托成功,未完全成功
        300 刚创建订单的时候
        400 已撤单
        500 服务器撤单/每日结算
        """
        return self.account.order_queue[self.account.order_queue['order_id'] == order_id_]['status']

    def QA_backtest_status(self):
        return vars(self)

    def QA_backtest_sell_all(self):
        __hold_list = pd.DataFrame(self.account.hold[1::], columns=self.account.hold[0]).set_index(
            'code', drop=False)['amount'].groupby('code').sum()

        for item in self.strategy_stock_list:
            try:
                if __hold_list[item] > 0:
                    self.QA_backtest_send_order(
                        self, item, __hold_list[item], -1, {'bid_model': 'C'})

            except:
                pass

    @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.__sync_order_LM(__backtest_cls, 'init_')  # 初始化事件

            if __backtest_cls.backtest_type in ['day', 'd', 'index_day']:

                func(*arg, **kwargs)  # 发委托单
                __backtest_cls.__sell_from_order_queue(__backtest_cls)
            elif __backtest_cls.backtest_type in ['1min', '5min', '15min', '30min', '60min', 'index_1min', 'index_5min', 'index_15min', 'index_30min', 'index_60min']:
                if __backtest_cls.backtest_type in ['1min', 'index_1min']:
                    type_ = '1min'
                elif __backtest_cls.backtest_type in ['5min', 'index_5min']:
                    type_ = '5min'
                elif __backtest_cls.backtest_type in ['15min', 'index_15min']:
                    type_ = '15min'
                elif __backtest_cls.backtest_type in ['30min', 'index_30min']:
                    type_ = '30min'
                elif __backtest_cls.backtest_type in ['60min', 'index_60min']:
                    type_ = '60min'
                daily_min = QA_util_make_min_index(
                    __backtest_cls.today, type_)  # 创造分钟线index
                # print(daily_min)
                for min_index in daily_min:
                    __backtest_cls.now = min_index
                    QA_util_log_info(
                        '=================Min hold list====================')
                    QA_util_log_info('in the begining of %s' % str(min_index))
                    QA_util_log_info(
                        tabulate(__backtest_cls.account.message['body']['account']['hold']))
                    func(*arg, **kwargs)  # 发委托单

                    __backtest_cls.__sell_from_order_queue(__backtest_cls)
                    if __backtest_cls.backtest_type in ['index_1min', 'index_5min', 'index_15min']:
                        __backtest_cls.__sync_order_LM(__backtest_cls, 't_0')
            __backtest_cls.__sync_order_LM(
                __backtest_cls, 'daily_settle')  # 每日结算

        # 最后一天
        __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):
        def __before_backtest(__backtest_cls, *arg, **kwargs):
            func(*arg, **kwargs)
            __backtest_cls.__QA_backtest_start(__backtest_cls)
        return __before_backtest(__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)
Esempio n. 20
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)