def ondata(self): ap.sound(f'entry: ondata') for idx, r in self.df.iterrows(): if idx % 500 == 0: print(f'{idx} / {len(self.df)}') idx -= 1 self.ths_yield_l = [0] self.ths_yield_s = [0] self.ths_avlb = self.ac.avlb[-1] self.ths_pst_l = self.ac.pst_l[-1] self.ths_pst_s = self.ac.pst_s[-1] if idx > 0: td.flatlong(self, idx, r) td.flatshort(self, idx, r) td.holdlong(self, r) td.holdshort(self, r) td.openlong(self, idx, r) td.openshort(self, idx, r) ths_yield_ls_res = sum(self.ths_yield_l) + sum(self.ths_yield_s) ths_ttas_res = self.ths_avlb + (self.ths_pst_l + self.ths_pst_s) * r.close self.pc.close.append(r.close) self.yd.ttas.append(ths_ttas_res) self.yd.eac_ls_pf.append(ths_yield_ls_res) self.yd.eac_l_pf.append(sum(self.ths_yield_l)) self.yd.eac_s_pf.append(sum(self.ths_yield_s)) self.ac.avlb.append(self.ths_avlb) self.ac.pst_l.append(self.ths_pst_l) self.ac.pst_s.append(self.ths_pst_s)
def idx_signal(self): ap.sound(f'entry: idx_signal') # l_sma13 = talib.SMA(self.l_close, 13) # l_sma89 = talib.SMA(self.l_close, 89) # self.l_signal_longopen = (self.l_close > l_sma13) & (l_sma13 > l_sma89) # self.l_signal_longflat = (self.l_close < l_sma13) return self
def evaluation(self): ap.sound(f'entry: create_df') mdd = empyrical.max_drawdown(self.df.eac_stgy_rt) stgy_ret_an = empyrical.annual_return(self.df.eac_stgy_rt, annualization=self.cls.annualization) bcmk_ret_an = empyrical.annual_return(self.df.eac_bcmk_rt, annualization=self.cls.annualization) stgy_vlt_an = empyrical.annual_volatility(self.df.eac_stgy_rt, annualization=self.cls.annualization) bcmk_vlt_an = empyrical.annual_volatility(self.df.eac_bcmk_rt, annualization=self.cls.annualization) calmar = empyrical.calmar_ratio(self.df.eac_stgy_rt, annualization=self.cls.annualization) omega = empyrical.omega_ratio(self.df.eac_stgy_rt, risk_free=self.cls.rf, annualization=self.cls.annualization) sharpe = qp.sharpe_ratio(stgy_ret_an, self.df.cum_stgy_rt, self.cls.rf) sortino = empyrical.sortino_ratio(self.df.eac_stgy_rt, annualization=self.cls.annualization) dsrk = empyrical.downside_risk(self.df.eac_stgy_rt, annualization=self.cls.annualization) information = empyrical.information_ratio(self.df.eac_stgy_rt, factor_returns=self.df.eac_bcmk_rt) beta = empyrical.beta(self.df.eac_stgy_rt, factor_returns=self.df.eac_bcmk_rt, risk_free=self.cls.rf) tail_rt = empyrical.tail_ratio(self.df.eac_stgy_rt) alpha = qp.alpha_ratio(stgy_ret_an, bcmk_ret_an, self.cls.rf, beta) stgy_ttrt_rt = (self.cls.yd.ttas[-1] - self.cls.yd.ttas[0]) / self.cls.yd.ttas[0] bcmk_ttrt_rt = (self.cls.pc.close[-1] - self.cls.pc.close[0]) / self.cls.pc.close[0] car_rt = stgy_ttrt_rt - bcmk_ttrt_rt car_rt_an = stgy_ret_an - bcmk_ret_an self.cls.df_output = pd.DataFrame( {'sgty_ttrt_rt': [stgy_ttrt_rt], 'bcmk_ttrt_rt': [bcmk_ttrt_rt], 'car_rt': [car_rt], 'stgy_ret_an': [stgy_ret_an], 'bcmk_ret_an': [bcmk_ret_an], 'car_rt_an': [car_rt_an], 'stgy_vlt_an': [stgy_vlt_an], 'bcmk_vlt_an': [bcmk_vlt_an], 'mdd': [mdd], 'sharpe': [sharpe], 'alpha': [alpha], 'beta': [beta], 'information': [information], 'tail_rt': [tail_rt], 'calmar': [calmar], 'omega': [omega], 'sortino': [sortino], 'dsrk': [dsrk]}) print(f'feedback: \n{self.cls.df_output.T}')
def backtest(self): ap.sound(f'entry: backtest') self.l_signal = self.l_signal.shift(1).fillna(0).astype(int) self.l_each_strategy_pct = self.l_signal * self.l_each_benchmark_pct self.l_cum_strategy_pct = ( 1 + np.array(self.l_each_strategy_pct)).cumprod() return self
def process(tushare, dict_replace_columns, year_tradeday=25, path_r=None): ap.sound(f'entry: process') pro = ts.pro_api() df = pd.DataFrame() df_code = pro.query('stock_basic', exchange='', list_status='L', fields='ts_code') l_code = df_code['ts_code'].tolist() # for i in range(len(l_code)): for i in range(200): print(f'i: {i} / {len(l_code)}') tushare['code'] = l_code[i] hss = HSStrategy(year_tradeday=year_tradeday, path_r=path_r, tushare=tushare, dict_replace_columns=dict_replace_columns) hss.run() hss.df_output['code'] = l_code[i] print(hss.df_output) if i == 0: df = hss.df_output.copy() else: df = ap.concat(df, hss.df_output) df.to_csv(r'../../../../data/res.csv')
def backtest(self): ap.sound(f'entry: backtest') self.l_signal = self.l_signal.shift(1).fillna(0).astype(int) l_signal_pct = self.l_signal * self.l_each_close_pct self.l_yield = (1 + l_signal_pct).cumprod() self.l_close_norm = (1 + np.array(self.l_each_close_pct[1:])).cumprod() return self
def asd_benchmark(self): ap.sound(f'entry: asd_data') self.l_close = self.df['close'].copy() self.l_each_close_pct = self.l_close.pct_change().tolist() self.l_each_benchmark_pct = self.l_each_close_pct self.l_each_benchmark_pct[0] = 0 self.l_cum_benchmark_pct = (1 + np.array(self.l_each_benchmark_pct)).cumprod() return self
def idx_signal(self): ap.sound(f'entry: idx_signal') dif, dea, hist = talib.MACD(self.df['close']) # ema12 = talib.EMA(self.df['close'], 12) l_ema26 = talib.EMA(self.df['close'], 26) m5 = talib.SMA(self.df['close'], 5) self.l_signal = (hist < 0) & (dea < 0) return self
def draw_back(self): ap.sound(f'entry: draw_back') plt.figure(figsize=(18, 8)) plt.plot(self.l_close_norm, color='teal') plt.plot(self.l_yield, color='grey') plt.legend(['benchmark', 'strategy yield curve'], loc="best") plt.show() return self
def output(self): ap.sound(f'entry: output') self.df_output['基准总收益率'] = [np.round(self.benchmark_total_pct, 4)] self.df_output['基准年化收益率'] = [np.round(self.benchmark_ytd_pct, 4)] self.df_output['策略总收益率'] = [np.round(self.strategy_total_pct, 4)] self.df_output['策略年化收益率'] = [np.round(self.strategy_ytd_pct, 4)] self.df_output['年化超额收益率'] = [np.round(self.car_pct, 4)] self.df_output['胜率'] = [np.round(self.win_pct, 4)] self.df_output['盈亏比'] = [np.round(self.win_pct, 4)] self.df_output['夏普比率'] = [np.round(self.sharpe_pct, 4)] self.df_output['Alpha'] = [np.round(self.alpha, 4)] self.df_output['Bate'] = [np.round(self.bate, 4)] self.df_output['信息比率'] = [np.round(self.info_pct, 4)] self.df_output['策略波动率'] = [np.round(self.strategy_volatility, 4)] self.df_output['基准波动率'] = [np.round(self.benchmark_volatility, 4)] self.df_output['最高浮动收益率'] = [np.round(self.max_each_strategy_pct, 4)] self.df_output['最低浮动收益率'] = [np.round(self.min_each_strategy_pct, 4)] self.df_output['最大回撤率'] = [np.round(self.mdd_pct, 4)] self.df_output['最大回撤位置'] = [self.mdd_range] self.df_output['最大收益率'] = [self.mrg_pct] self.df_output['最大收益位置'] = [self.mrg_range] self.df_output['总交易次数'] = [len(self.l_stroke_pct)] self.df_output['平均收益率'] = [self.avg_stroke_pct] self.df_output['平均持仓时间'] = [self.avg_holdtime] self.df_output['盈利交易次数'] = [self.profit_num] self.df_output['盈利交易平均收益率'] = [self.profit_avg_pct] self.df_output['盈利交易平均持仓时间'] = [self.profit_avg_holdtime] self.df_output['亏损交易次数'] = [self.loss_num] self.df_output['亏损交易平均收益率'] = [self.loss_avg_pct] self.df_output['亏损交易平均持仓时间'] = [self.loss_avg_holdtime] self.df_output['总测试天数'] = [self.kline_num] self.df_output['持仓天数'] = [self.tradeday_num] self.df_output['持仓天数占比'] = [self.tradeday_pct] print( f'基准总收益率: {np.round(self.benchmark_total_pct, 4)}, 策略总收益率: {np.round(self.strategy_total_pct, 4)}\n' f'基准年化收益率: {np.round(self.benchmark_ytd_pct, 4)}, 策略年化收益率: {np.round(self.strategy_ytd_pct, 4)}\n' f'年化超额收益率: {np.round(self.car_pct, 4)}, 胜率: {np.round(self.win_pct, 4)}, ' f'盈亏比: {np.round(self.win_pct, 4)}\n' f'夏普比率: {np.round(self.sharpe_pct, 4)}, Alpha: {np.round(self.alpha, 4)}, ' f'Bate: {np.round(self.bate, 4)}, 信息比率: {np.round(self.info_pct, 4)}\n' f'策略波动率: {np.round(self.strategy_volatility, 4)}, 基准波动率: {np.round(self.benchmark_volatility, 4)}\n' f'最高浮动收益率: {np.round(self.max_each_strategy_pct, 4)}, ' f'最低浮动收益率: {np.round(self.min_each_strategy_pct, 4)}\n' f'最大回撤率: {np.round(self.mdd_pct, 4)}, 最大回撤位置: {self.mdd_range}\n' f'最大收益率: {np.round(self.mrg_pct, 4)}, 最大收益位置: {self.mrg_range}\n' f'总交易次数: {len(self.l_stroke_pct)}, 平均收益率: {np.round(self.avg_stroke_pct, 4)}, ' f'平均持仓时间: {np.round(self.avg_holdtime, 4)}\n' f'盈利交易次数: {self.profit_num}, 盈利交易平均收益率: {np.round(self.profit_avg_pct, 4)}, ' f'盈利交易平均持仓时间: {np.round(self.profit_avg_holdtime, 4)}\n' f'亏损交易次数: {self.loss_num}, 亏损交易平均收益率: {np.round(self.loss_avg_pct, 4)}, ' f'亏损交易平均持仓时间: {np.round(self.loss_avg_holdtime, 4)}\n' f'总测试天数: {self.kline_num}, 持仓天数: {self.tradeday_num}, 持仓天数占比: {np.round(self.tradeday_pct, 2)}' ) return self
def draw_price(self): ap.sound(f'entry: draw_price') plt.figure(figsize=(18, 8)) ax_close = plt.subplot(2, 1, 1) ax_close.plot(self.df['close'], color='teal') ax_pct = plt.subplot(2, 1, 2) ax_pct.plot(self.l_each_close_pct, color='grey') plt.show() return self
def create_df(self): ap.sound(f'entry: create_df') self.df['datetime'] = self.cls.df.datetime self.df['eac_stgy_rt'] = self.cls.yd.eac_stgy_rt self.df['cum_stgy_rt'] = self.cls.yd.cum_stgy_rt self.df['eac_bcmk_rt'] = self.cls.yd.eac_bcmk_rt self.df['cum_bcmk_rt'] = self.cls.yd.cum_bcmk_rt self.df.set_index('datetime', inplace=True)
def run(self): self.asd_data() self.idx_signal() self.backtest() self.feedback() # dr.draw_price(self.l_close, self.l_each_benchmark_pct) # dr.draw_signal(self.l_close, self.l_signal) # dr.draw_srtoke_distribution(self.fb.l_stroke_holdtime, self.fb.l_stroke_pct) dr.draw_back(np.array(self.l_close), self.l_cum_strategy_pct) ap.sound(f'finished') return self
def stgy_rt(cls): ap.sound(f'entry: stgy_rt') df = pd.DataFrame() df['ttas'] = cls.yd.ttas df['shift'] = cls.yd.ttas df['shift'] = df['shift'].shift(1).fillna(0) df['eac_stgy_rt'] = df.apply(lambda x: (x['ttas'] - x['shift']) / x['shift'] if x['shift'] != 0 else 0, axis=1) cls.yd.eac_stgy_rt = df.eac_stgy_rt.values cls.yd.cum_stgy_rt = (1 + cls.yd.eac_stgy_rt).cumprod()
def run(self): self.get_data() self.asd_data() self.idx_signal() self.backtest() self.feedback() # self.draw_price() # self.draw_signal() self.draw_back() ap.sound(f'finished') return self
def draw_signal(self): ap.sound(f'entry: draw_signal') plt.figure(figsize=(18, 12)) ax_close = plt.subplot(2, 1, 1) ax_close.plot(self.df['close'], color='teal') ax_signal = plt.subplot(2, 1, 2) ax_signal.bar(x=self.l_signal.index, height=self.l_signal.values, color='grey') plt.show() return self
def bcmk_rt(cls): ap.sound(f'entry: bcmk_rt') df = pd.DataFrame() df['bcmk'] = cls.df.close df['shift'] = cls.df.close df['shift'] = df['shift'].shift(1).fillna(0) df['eac_bcmk_rt'] = df.apply(lambda x: (x['bcmk'] - x['shift']) / x['shift'] if x['shift'] != 0 else 0, axis=1) cls.yd.eac_bcmk_rt = df.eac_bcmk_rt.values cls.yd.cum_bcmk_rt = (1 + cls.yd.eac_bcmk_rt).cumprod()
def stat_yield(self): """ 统计收益率相关指标: l_stroke_pct: 每一笔交易的收益率 avg_stroke_pct: 总体平均收益率 profit_num: 盈利交易次数 profit_avg_pct: 盈利交易平均收益率 loss_num: 亏损交易次数 loss_avg_pct: 亏损交易平均收益率 win_pct: 胜率 winloss_pct: 盈亏比 strategy_total_pct: 策略总收益率 strategy_ytd_pct: 策略年化收益率 benchmark_total_pct: 基准总收益率 benchmark_ytd_pct: 基准年化收益率 car_pct: 超额收益率 max_each_strategy_pct: 策略单笔交易最大收益率 min_each_strategy_pct: 策略单笔交易最小收益率 :return: """ ap.sound(f'entry: stat_strategy') self.l_stroke_pct = [ sum(self.l_each_benchmark_pct[x:y]) for (x, y) in self.l_holdidx ] self.avg_stroke_pct = float(np.mean(self.l_stroke_pct)) self.profit_num = len([i for i in self.l_stroke_pct if i > 0]) self.profit_avg_pct = float( np.mean([i for i in self.l_stroke_pct if i > 0])) self.loss_num = len([i for i in self.l_stroke_pct if i <= 0]) self.loss_avg_pct = float( np.mean([i for i in self.l_stroke_pct if i <= 0])) self.win_pct = self.profit_num / (self.profit_num + self.loss_num) self.winloss_pct = abs( sum([i for i in self.l_stroke_pct if i > 0]) / sum([i for i in self.l_stroke_pct if i <= 0])) self.strategy_total_pct = (self.l_cum_strategy_pct[-1] - self.l_cum_strategy_pct[0]) / np.mean( self.l_cum_strategy_pct[0:5]) self.strategy_ytd_pct = qp.annualized(self.strategy_total_pct, self.year_tradeday, self.kline_num) self.benchmark_total_pct = ( self.l_close[-1] - self.l_close[0]) / np.mean(self.l_close[0:5]) self.benchmark_ytd_pct = qp.annualized(self.benchmark_total_pct, self.year_tradeday, self.kline_num) self.car_pct = self.strategy_ytd_pct - self.benchmark_ytd_pct self.max_each_strategy_pct = max(self.l_cum_strategy_pct) self.min_each_strategy_pct = min(self.l_cum_strategy_pct) return self
def get_data(self): ap.sound(f'entry: get_data') if self.path_r: self.df = pd.read_csv(self.path_r, encoding='gb18030') elif self.tushare: pro = ts.pro_api(TushareToken) self.df = pro.daily(ts_code=self.tushare['code'], start_date=self.tushare['start_datetime'], end_date=self.tushare['end_datetime']) else: print(f'ERROR: Input read path is empty') print(f'columns_name: {self.df.columns.tolist()}') return self
def data_process(self): ap.sound(f'entry: get_data') if self.dict_replace_columns: self.df.rename(columns=self.dict_replace_columns, inplace=True) self.df = self.df[['datetime', 'open', 'high', 'low', 'close']].copy() self.df['datetime'] = pd.to_datetime(self.df['datetime']) self.df.sort_values(by='datetime', inplace=True) # self.df = self.df.set_index('datetime') # self.df.to_csv(r'../../../../data/20200708.csv') print(f'df: \n{self.df.head()}') return self
def draw_back(l_cum_benchmark_pct, l_cum_strategy_pct): """ 画出策略收益曲线和基准收益曲线 :param l_cum_benchmark_pct: array, 累计基准收益率 :param l_cum_strategy_pct: array, 累计策略收益率 :return: """ ap.sound(f'entry: draw_back') plt.figure(figsize=(18, 8)) plt.plot(l_cum_benchmark_pct, color='teal') plt.plot(l_cum_strategy_pct, color='grey') plt.legend(['benchmark', 'strategy yield curve'], loc="best") plt.show()
def draw_signal(l_close, l_signal): """ 画出基准价格和信号 :param l_close: DataFrame.Series, 收盘价列表 :param l_signal: DataFrame.Series, 信号列表 :return: """ ap.sound(f'entry: draw_signal') plt.figure(figsize=(18, 8)) ax_close = plt.subplot(2, 1, 1) ax_close.plot(l_close, color='teal') ax_signal = plt.subplot(2, 1, 2) ax_signal.bar(x=l_signal.index, height=l_signal.values, color='grey') plt.show()
def draw_price(l_close, l_each_benchmark_pct): """ 画出基准曲线图 :param l_close: DataFrame.Series, 收盘价列表 :param l_each_benchmark_pct: list, 基准每个Kline的change_pct :return: """ ap.sound(f'entry: draw_price') plt.figure(figsize=(18, 8)) ax_close = plt.subplot(2, 1, 1) ax_close.plot(l_close, color='teal') ax_pct = plt.subplot(2, 1, 2) ax_pct.plot(l_each_benchmark_pct, color='grey') plt.show()
def get_data(self): ap.sound(f'entry: get_data') self.df = pd.read_excel(self.path_r, encoding='gb18030') self.df = self.df.set_index('datetime') print(f'df: \n{self.df.head()}') token = '31e528be4a85855ac2408fb477b2e9bd0f083e53461f1119d6e9619f' pro = ts.pro_api(token) # df = pro.daily(ts_code='000001.SZ', start_date='20190101', end_date='20190630') # print(df.head()) # self.df = df[['trade_date', 'pre_close']].copy() # self.df.rename(columns={'trade_date': 'datetime', 'pre_close': 'close'}, inplace=True) # self.df.sort_values(by='datetime', inplace=True) # self.df = self.df.set_index('datetime') return self
def stat_maxtrade(self): """ 统计最大回撤交易和最大增长交易 mdd_pct: 最大回撤比率 mdd_range: 最大回撤所在序列范围 mrg_pct: 最大增长比率 mrg_range: 最大增长所在序列范围 :return: """ ap.sound(f'entry: stat_maxtrade') mdd = qp.maxdrawdown(np.array(self.l_cum_strategy_pct)) self.mdd_pct = mdd['ratio'] self.mdd_range = mdd['range'] mrg = qp.maxrevenuegrowth(np.array(self.l_cum_strategy_pct)) self.mrg_pct = mrg['ratio'] self.mrg_range = mrg['range'] return self
def draw_srtoke_distribution(l_stroke_holdtime, l_stroke_pct): """ 画出每一笔交易的收益和持仓时间分布图 :param l_stroke_holdtime: array, 每笔交易的持有时间列表 :param l_stroke_pct: array, 每笔交易的收益 :return: """ ap.sound(f'entry: draw_srtoke_distribution') df = pd.DataFrame() df['holdtime'] = l_stroke_holdtime df['strategy'] = l_stroke_pct with sns.axes_style("dark"): sns.jointplot('holdtime', 'strategy', data=df, kind='kde', color='grey', space=0, pct=6) plt.show()
def stat_holdtime(self): """ 统计持仓时间相关指标: l_stroke_holdtime: 每一笔交易持仓时间 avg_holdtime: 总平均持仓时间 profit_avg_holdtime: 盈利交易平均持仓时间 loss_avg_holdtime: 亏损交易平均持仓时间 :return: """ ap.sound(f'entry: stat_holdtime') self.l_stroke_holdtime = [y - x for (x, y) in self.l_holdidx] self.avg_holdtime = np.mean(self.l_stroke_holdtime) arr_stroke_pct = np.array(self.l_stroke_pct) arr_stroke_holdtime = np.array(self.l_stroke_holdtime) arr_profit = arr_stroke_holdtime[arr_stroke_pct > 0] arr_loss = arr_stroke_holdtime[arr_stroke_pct <= 0] self.profit_avg_holdtime = np.mean(arr_profit) self.loss_avg_holdtime = np.mean(arr_loss) return self
def get_holdidx(self): """ 获取持仓索引列表l_holdidx l_aux eg: [1, 3, 4, 6, 7, 8], 两两一组,表示开仓和平仓的索引,第一笔交易开仓1,平仓3,第二笔交易开仓4,平仓6... l_holdidx: [[4, 6], [8, 20], [45, 47]], 每一个元素表示一次交易,内层列表表示每一次交易开始和结束的索引 process: l_signal是信号布尔列表->aux,1表示满足开仓条件,持有仓位,0表示不满足开仓条件,没有仓位 l_signal平移一位相减,得到的列表只包括1/0/-1三种情况,1为开仓,-1为平仓, 存在一种特殊情况,最后一次开仓后,在测试数据范围内没有满足平仓条件,需要判断后补上一个-1表示平仓 信号布尔列表aux只保留1和-1值后,得到的就是开平仓的列表,取其索引得到l_aux 通过l_aux获取到l_holdidx :return: """ ap.sound(f'entry: get_holdidx') df_temp = pd.DataFrame() df_temp['sig'] = self.l_signal df_temp['sig_shift'] = self.l_signal df_temp['sig_shift'] = df_temp['sig_shift'].shift(1).fillna(0).astype( int) df_temp['aux'] = df_temp['sig'] - df_temp['sig_shift'] df_temp = df_temp.reset_index() df_aux = df_temp[['aux']][df_temp['aux'] != 0].reset_index().copy() if df_aux.loc[len(df_aux) - 1, 'aux'] == 1: df_aux = ap.concat( df_aux, pd.DataFrame({ 'index': [df_aux['index'].tolist()[-1] + 1], 'aux': [-1] })) l_aux = df_aux['index'].tolist() step = 2 self.l_holdidx = [ l_aux[i:i + step] for i in range(0, len(l_aux), step) ] print(f'df_aux: \n{df_aux.head()}') print(f'tradenum: {len(df_aux) / 2}') return self