Example #1
0
 def run(self):
     start_time = datetime.datetime.now()
     self.initialize_strategy()
     self.code_range = self.code_range.loc[self.code_range['industry'] ==
                                           '银行(中信)']
     self.code_range.reset_index(inplace=True)
     overall_factor = self.factors_combination()
     selection = self.select_codes(overall_factor)
     selection.to_csv(self.folder_dir + 'code_selection.csv',
                      encoding='gbk')
     bm_stk_wgt = self.get_next_bm_stk_wgt()
     bm_stk_wgt.to_csv(self.folder_dir + 'bm_wgt.csv', encoding='gbk')
     # --------------------------backtest--------------------------------
     bt_start = selection.index[0].strftime('%Y%m%d')
     bt_end = bm_stk_wgt.index[-1].strftime('%Y%m%d')
     QE = BacktestEngine(self.strategy_name,
                         bt_start,
                         bt_end,
                         self.adj_interval,
                         self.benchmark,
                         stock_capital=self.capital)
     pvs = []
     portfolio_value = QE.run(selection, bt_start, bt_end)
     portfolio_value = portfolio_value.loc[:, ['TotalValue']]
     portfolio_value.rename(columns={'TotalValue': 'AlphaBank'},
                            inplace=True)
     pvs.append(portfolio_value)
     QE.stk_portfolio.reset_portfolio(self.capital)
     portfolio_value = QE.run(bm_stk_wgt, bt_start, bt_end)
     portfolio_value = portfolio_value.loc[:, ['TotalValue']]
     portfolio_value.rename(columns={'TotalValue': 'BmBank'}, inplace=True)
     pvs.append(portfolio_value)
     banks_comparation = pd.concat(pvs, axis=1)
     banks_comparation['AccumAlpha'] = \
         DataProcess.calc_accum_alpha(banks_comparation['AlphaBank'], banks_comparation['BmBank']) - 1
     banks_comparation.to_csv(self.folder_dir + 'banks_comaration.csv',
                              encoding='gbk')
     self.logger.info('Bank Comparation:')
     self.logger.info('-ANN_Alpha: %f' % DataProcess.calc_alpha_ann_return(
         banks_comparation['AlphaBank'], banks_comparation['BmBank']))
     MDD, MDD_period = \
         DataProcess.calc_alpha_max_draw_down(banks_comparation['AlphaBank'], banks_comparation['BmBank'])
     self.logger.info('-Alpha_MDD: %f' % MDD)
     self.logger.info('-Alpha_MDD period: %s - %s' %
                      (MDD_period[0], MDD_period[1]))
     self.logger.info('-Alpha_sharpe: %f' % DataProcess.calc_alpha_sharpe(
         banks_comparation['AlphaBank'], banks_comparation['BmBank']))
     print('Time used:', datetime.datetime.now() - start_time)
Example #2
0
 def all_indu_run(self, stk_weight, start, end):
     # folder
     if os.path.exists(self.dir + '/industry_alpha/'):
         pass
     else:
         os.makedirs(self.dir + '/industry_alpha')
     # logger
     self.indu_logger = logging.getLogger('industry_log')
     self.indu_logger.setLevel(level=logging.INFO)
     file_name = 'industry.log'
     handler = logging.FileHandler(self.dir + '/industry_alpha/' +
                                   file_name)
     handler.setLevel(logging.INFO)
     console = logging.StreamHandler()
     formatter = logging.Formatter(
         '%(asctime)s - %(levelname)s: %(message)s')
     handler.setFormatter(formatter)
     handler.setFormatter(formatter)
     self.indu_logger.addHandler(handler)
     self.indu_logger.addHandler(console)
     # industry 为统计用数据
     measure = 'industry'
     industry = self.influx.getDataMultiprocess(self.DB, measure,
                                                self.start, self.end,
                                                ['code', self.indu_field])
     industry.index.names = ['date']
     industry.rename(columns={self.indu_field: 'industry'}, inplace=True)
     # benchmark comp
     measure = 'index_weight'
     bm_comp = self.influx.getDataMultiprocess(self.DB, measure, self.start,
                                               self.end)
     bm_comp = bm_comp.loc[bm_comp['index_code'] == self.benchmark_code,
                           ['code', 'weight']]
     former_date_dict = dict(
         zip(bm_comp.index.unique()[1:],
             bm_comp.index.unique()[:-1]))
     bm_comp['date'] = bm_comp.index
     bm_comp['date'] = bm_comp['date'].map(former_date_dict)
     bm_comp = bm_comp.dropna(subset=['date'])
     bt_start = max(bm_comp.index.unique()[0],
                    stk_weight.index.unique()[0],
                    pd.to_datetime(str(start)))
     bt_end = min(bm_comp.index.unique()[-1],
                  stk_weight.index.unique()[-1], pd.to_datetime(str(end)))
     # merge industry
     bm_comp = pd.merge(bm_comp,
                        industry.reset_index(),
                        on=['date', 'code'])
     stk_weight = pd.merge(stk_weight.reset_index(),
                           industry.reset_index(),
                           on=['date', 'code'])
     for indu in bm_comp['industry'].unique():
         bm_comp_indu = bm_comp.loc[bm_comp['industry'] == indu,
                                    ['date', 'code', 'weight']].copy()
         indu_weight = bm_comp_indu.groupby(
             'date')['weight'].sum().to_dict()
         bm_comp_indu['bm_weight'] = bm_comp_indu['date'].map(indu_weight)
         bm_comp_indu['weight'] = bm_comp_indu['weight'] / bm_comp_indu[
             'bm_weight'] * 100
         bm_comp_indu = bm_comp_indu.loc[:, ['date', 'code', 'weight']]
         bm_comp_indu.set_index('date', inplace=True)
         stk_weight_indu = stk_weight.loc[
             stk_weight['industry'] == indu,
             ['date', 'code', 'weight']].copy()
         indu_weight = stk_weight_indu.groupby(
             'date')['weight'].sum().to_dict()
         stk_weight_indu['bm_weight'] = stk_weight_indu['date'].map(
             indu_weight)
         stk_weight_indu['weight'] = stk_weight_indu[
             'weight'] / stk_weight_indu['bm_weight'] * 100
         stk_weight_indu = stk_weight_indu.loc[:,
                                               ['date', 'code', 'weight']]
         stk_weight_indu.set_index('date', inplace=True)
         # ===========================================================================================
         bm_value, _ = self.run(bm_comp_indu, bt_start, bt_end,
                                'bm_{0}'.format(indu))
         self.stk_portfolio.reset_portfolio(self.stock_capital)
         alpha_value, _ = self.run(stk_weight_indu, bt_start, bt_end,
                                   'alpha_{0}'.format(indu))
         self.stk_portfolio.reset_portfolio(self.stock_capital)
         bm_value.rename(columns={'TotalValue': 'BmStkValue'}, inplace=True)
         bm_value = bm_value.loc[:, ['BmStkValue']]
         alpha_value.rename(columns={'TotalValue': 'AlphaValue'},
                            inplace=True)
         alpha_value = alpha_value.loc[:, ['AlphaValue']]
         merge_value = pd.merge(bm_value.reset_index(),
                                alpha_value.reset_index(),
                                on=['date'])
         merge_value['AccumAlpha'] = \
             DataProcess.calc_accum_alpha(merge_value['AlphaValue'], merge_value['BmStkValue']) - 1
         merge_value.set_index('date', inplace=True)
         filename = self.dir + '/industry_alpha/{0}.csv'.format(indu)
         merge_value.to_csv(filename, encoding='gbk')
         self.indu_logger.info('INDUSTRY: {0}'.format(indu))
         self.indu_logger.info(
             '-ANN_Alpha: %f' % DataProcess.calc_alpha_ann_return(
                 merge_value['AlphaValue'], merge_value['BmStkValue']))
         MDD, MDD_period = DataProcess.calc_alpha_max_draw_down(
             merge_value['AlphaValue'], merge_value['BmStkValue'])
         self.indu_logger.info('-Alpha_MDD: %f' % MDD)
         self.indu_logger.info('-Alpha_MDD period: %s - %s' %
                               (MDD_period[0], MDD_period[1]))
         self.indu_logger.info(
             '-Alpha_sharpe: %f' % DataProcess.calc_alpha_sharpe(
                 merge_value['AlphaValue'], merge_value['BmStkValue']))
         self.indu_logger.info('-' * 50)
Example #3
0
 def run(self, stk_weight, start, end, name=None):
     backtest_starttime = datetime.datetime.now()
     # 默认输入的权重 以日期为index
     stk_weight.index.names = ['date']
     mkt = self.market.loc[str(start):str(end), :].copy()
     mkt_with_weight = pd.merge(mkt.reset_index(),
                                stk_weight.reset_index(),
                                on=['date', 'code'],
                                how='left')
     mkt_with_weight['weight'] = mkt_with_weight['weight'].fillna(0)
     mkt_with_weight.set_index('date', inplace=True)
     mkt_with_weight.sort_index(inplace=True)
     calendar = mkt_with_weight.index.unique().strftime('%Y%m%d')
     # backtest begins
     day_counter = 0
     positions_dict = {}
     portfolio_value_dict = {}
     balance, stk_value, total_value = self.stk_portfolio.get_portfolio_value(
         price_input=pd.Series([]))
     # 记录 benchmark 净值
     benchmark_networth = self.benchmark_quote[calendar[0]]
     benchmark_start_value = total_value
     for trade_day in calendar:
         self.logger.info('Trade Day: %s' % trade_day)
         day_mkt_with_weight = mkt_with_weight.loc[mkt_with_weight.index ==
                                                   trade_day, :].copy()
         day_mkt_with_weight.set_index('code', inplace=True)
         day_ex_right = self.exright.loc[self.exright.index ==
                                         trade_day, :].copy()
         trade_amount = 0
         # 开盘前处理除权除息分红送股
         self.stk_portfolio.process_ex_right(day_ex_right)
         # 没有行情且在position里的stk记为退市,并统计退市金额
         delist_amount = 0
         no_quote_stks = set(self.stk_portfolio.stk_positions.keys()) - set(
             day_mkt_with_weight.index)
         for stk in no_quote_stks:
             delist_amount += self.stk_portfolio.stk_positions[stk]['volume'] * \
                              self.stk_portfolio.stk_positions[stk]['latest_close']
         # 退市股票的金额需在stock_value中剔除,以免资金占用
         target_capital = (1 - self.cash_reserve_rate) * (total_value +
                                                          delist_amount)
         # 计算目标成交量
         day_mkt_with_weight['target_volume'] = \
             target_capital * day_mkt_with_weight['weight'] / 100 / day_mkt_with_weight['preclose']
         # -----------------------------------------------------------------------------------------------
         # 每隔x天调仓
         if day_counter % self.adj_interval == 0:
             # 记录没法交易的股票
             # 记录 (weight不为0 或者 已在position中) 且 状态停牌 的票
             codes = day_mkt_with_weight.loc[
                 ((day_mkt_with_weight['target_volume'] > 0) |
                  (day_mkt_with_weight.index.isin(self.stk_portfolio.
                                                  stk_positions.keys()))) &
                 (day_mkt_with_weight['status'] == '停牌'), :].index.values
             weights = day_mkt_with_weight.loc[
                 ((day_mkt_with_weight['target_volume'] > 0) |
                  (day_mkt_with_weight.index.isin(self.stk_portfolio.
                                                  stk_positions.keys()))) &
                 (day_mkt_with_weight['status'] == '停牌'), 'weight'].values
             untradeable = dict(zip(codes, weights))
             # (weight不为0 或者 已在position中) 且 状态不停牌 的票
             tradeable_df = day_mkt_with_weight.loc[
                 ((day_mkt_with_weight['target_volume'] > 0) |
                  (day_mkt_with_weight.index.isin(self.stk_portfolio.
                                                  stk_positions.keys()))) &
                 (day_mkt_with_weight['status'] != '停牌'), :].copy()
             for code, row in tradeable_df.iterrows():
                 if (row['low'] == row['high']) and (row['high'] >= round(
                         row['preclose'] * 1.1, 2)):
                     price_limit = 'high'
                 elif (row['low'] == row['high']) and (row['low'] <= round(
                         row['preclose'] * 0.9, 2)):
                     price_limit = 'low'
                 else:
                     price_limit = 'no_limit'
                 # 因涨跌停没有交易成功的股票代码会返回
                 trade_res = self.stk_portfolio.trade_stks_to_target_volume(
                     trade_day, code, row[self.price_field],
                     row['target_volume'], price_limit)
                 # 记录可以交易,但是因为涨跌停无法交易 的票
                 if trade_res == 'Trade Fail':
                     untradeable[code] = row['weight']
                 # 记录双边的交易金额
                 else:
                     trade_amount += trade_res
         # 不是调仓日时,之前因涨跌停没买到的stks也要补
         else:
             # 没有行情的认为已退市,从失败列表中剔除
             no_quote_in_untradeable = set(untradeable.keys()) - set(
                 day_mkt_with_weight.index)
             for stk in no_quote_in_untradeable:
                 untradeable.pop(stk)
             # untradeable 不为空时,补买卖stk
             if untradeable:
                 tradeable_df = day_mkt_with_weight.loc[
                     (day_mkt_with_weight['status'] != '停牌')
                     & day_mkt_with_weight.index.isin(untradeable.keys()
                                                      ), :].copy()
                 if not tradeable_df.empty:
                     tradeable_df['weight'] = tradeable_df.index
                     tradeable_df['weight'] = tradeable_df['weight'].map(
                         untradeable)
                     tradeable_df['target_volume'] = \
                         target_capital * tradeable_df['weight'] / 100 / tradeable_df['preclose']
                     for code, row in tradeable_df.iterrows():
                         if (row['low']
                                 == row['high']) and (row['high'] >= round(
                                     row['preclose'] * 1.1, 2)):
                             price_limit = 'high'
                         elif (row['low']
                               == row['high']) and (row['low'] <= round(
                                   row['preclose'] * 0.9, 2)):
                             price_limit = 'low'
                         else:
                             price_limit = 'no_limit'
                         trade_res = self.stk_portfolio.trade_stks_to_target_volume(
                             trade_day, code, row[self.price_field],
                             row['target_volume'], price_limit)
                         if trade_res == 'Trade Fail':
                             pass
                         else:
                             trade_amount += trade_res
                             untradeable.pop(code)
         # -------------------------------------------------------------------------------
         # 处理 吸收合并
         day_swap = self.swap.loc[
             self.swap['code'].isin(self.stk_portfolio.stk_positions.keys())
             & (self.swap.index == trade_day), :].copy()
         if not day_swap.empty:
             for date, row in day_swap.iterrows():
                 swap_price = self.stk_portfolio.stk_positions[
                     row['code']]['price'] / row['swap_ratio']
                 swap_volume = round(
                     self.stk_portfolio.stk_positions[row['code']]['volume']
                     * row['swap_ratio'])
                 if row['swap_code'] in self.stk_portfolio.stk_positions:
                     merged_volume = self.stk_portfolio.stk_positions[
                         row['swap_code']]['volume'] + swap_volume
                     merged_price = (
                         self.stk_portfolio.stk_positions[row['swap_code']]
                         ['volume'] * self.stk_portfolio.stk_positions[
                             row['swap_code']]['price'] +
                         swap_volume * swap_price) / merged_volume
                     self.stk_portfolio.stk_positions[
                         row['swap_code']]['volume'] = merged_volume
                     self.stk_portfolio.stk_positions[
                         row['swap_code']]['price'] = merged_price
                 else:
                     self.stk_portfolio.stk_positions[row['swap_code']] = {}
                     self.stk_portfolio.stk_positions[
                         row['swap_code']]['volume'] = swap_volume
                     self.stk_portfolio.stk_positions[
                         row['swap_code']]['price'] = swap_price
                     self.stk_portfolio.stk_positions[row['swap_code']]['latest_close'] = \
                         self.stk_portfolio.stk_positions[row['code']]['latest_close'] / row['swap_ratio']
                 self.stk_portfolio.stk_positions.pop(row['code'])
         # 记录 已有持仓中停牌的stk
         suspend_stks_in_pos = \
             day_mkt_with_weight.loc[
             day_mkt_with_weight.index.isin(self.stk_portfolio.stk_positions.keys()) &
             (day_mkt_with_weight['status'] == '停牌'), :].index.values
         # 记录 benchmark value
         # 记录 portfolio value
         # 记录双边换手率 turnover
         benchmark_value = benchmark_start_value / benchmark_networth * self.benchmark_quote[
             trade_day]
         balance, stk_value, total_value = self.stk_portfolio.get_portfolio_value(
             day_mkt_with_weight['close'])
         if stk_value == 0:
             turnover = 0
         else:
             turnover = trade_amount / stk_value
         portfolio_value_dict[trade_day] = \
             {'Balance': balance, 'StockValue': stk_value, 'TotalValue': total_value,
              'DelistAmount': delist_amount, 'SuspendStk': len(suspend_stks_in_pos),
              'BenchmarkValue': benchmark_value, 'Turnover': turnover}
         self.logger.info(
             ' -Balance: %f \n -StockValue: %f \n -TotalValue %f \n -DelistAmount: %f \n'
             ' -SuspendStk: %i \n -BenchmarkValue: %f \n -Turnover: %f' %
             (balance, stk_value, total_value, delist_amount,
              len(suspend_stks_in_pos), benchmark_value, turnover))
         self.logger.info(
             '======================================================================='
         )
         # 记录 持仓
         positions_dict[trade_day] = copy.deepcopy(
             self.stk_portfolio.stk_positions)
         day_counter += 1
     # 输出持仓记录
     positions_dfs = []
     for time in positions_dict:
         trade_day_position = pd.DataFrame(positions_dict[time]).T
         trade_day_position['Time'] = time
         trade_day_position.index.name = 'Code'
         positions_dfs.append(trade_day_position.reset_index())
     positions = pd.concat(positions_dfs, ignore_index=True)
     positions['Time'] = pd.to_datetime(positions['Time'])
     positions.set_index('Time', inplace=True)
     if not name:
         filename = self.dir + 'Positions.csv'
     else:
         filename = self.dir + 'Positions_{0}.csv'.format(name)
     positions.to_csv(filename, encoding='gbk')
     # 输出净值
     portfolio_value = pd.DataFrame(portfolio_value_dict).T
     portfolio_value['AccumAlpha'] = \
         DataProcess.calc_accum_alpha(portfolio_value['TotalValue'], portfolio_value['BenchmarkValue']) - 1
     portfolio_value.index = pd.to_datetime(portfolio_value.index)
     portfolio_value.index.names = ['date']
     if not name:
         filename = self.dir + 'Value.csv'
     else:
         filename = self.dir + 'Value_{0}.csv'.format(name)
     portfolio_value.to_csv(filename, encoding='gbk')
     self.res_logger.info(
         'Backtest finish time: %s' %
         datetime.datetime.now().strftime('%Y/%m/%d - %H:%M:%S'))
     self.res_logger.info('*' * 50)
     self.res_logger.info('{0} PERFORMANCE:'.format(name))
     self.res_logger.info('-ANN_Alpha: %f' %
                          DataProcess.calc_alpha_ann_return(
                              portfolio_value['TotalValue'],
                              portfolio_value['BenchmarkValue']))
     MDD, MDD_period = DataProcess.calc_alpha_max_draw_down(
         portfolio_value['TotalValue'], portfolio_value['BenchmarkValue'])
     self.res_logger.info('-Alpha_MDD: %f' % MDD)
     self.res_logger.info('-Alpha_MDD period: %s - %s' %
                          (MDD_period[0], MDD_period[1]))
     self.res_logger.info(
         '-Alpha_sharpe: %f' %
         DataProcess.calc_alpha_sharpe(portfolio_value['TotalValue'],
                                       portfolio_value['BenchmarkValue']))
     print('Backtest finish! Time used: ',
           datetime.datetime.now() - backtest_starttime)
     return portfolio_value, positions