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 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: {df_aux.head()}') return self
def stat_otherindex(self): """ 计算其他指标: sharpe_pct: 夏普比率 info_pct: 信息比率 benchmark_volatility: 基准波动率 strategy_volatility: 策略波动率 bate: 贝塔值 alpha: 阿尔法值 :return: """ ap.sound(f'entry: stat_shape') self.sharpe_pct = qp.sharpe_ratio(self.strategy_ytd_pct, self.l_cum_strategy_pct, self.rf) self.info_pct = qp.information_ratio(self.l_each_strategy_pct, self.l_each_close_pct, self.strategy_ytd_pct, self.benchmark_ytd_pct, self.year_tradeday) self.benchmark_volatility = qp.volatility(self.l_each_close_pct, self.year_tradeday, self.kline_num) self.strategy_volatility = qp.volatility(self.l_each_strategy_pct, self.year_tradeday, self.kline_num) self.bate = qp.bate(self.l_each_strategy_pct, self.l_each_close_pct) self.alpha = qp.alpha(self.strategy_ytd_pct, self.benchmark_ytd_pct, self.rf, self.bate) return self
def draw_back(self): ap.sound(f'entry: draw_back') plt.figure(figsize=(18, 8)) plt.plot(self.l_cum_benchmark_pct, color='teal') plt.plot(self.l_cum_strategy_pct, color='grey') plt.legend(['benchmark', 'strategy yield curve'], loc="best") plt.show() 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_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 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 draw(self): ap.sound(f'entry: draw') df = pd.DataFrame() df['holdtime'] = self.l_each_holdtime df['strategy'] = self.l_each_strategy_pct with sns.axes_style("dark"): sns.jointplot('holdtime', 'strategy', data=df, kind='kde', color='grey', space=0, pct=6) plt.show() return self
def run(self): 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 stat_tradeday(self): """ 统计交易日相关指标: tradeday_num: 交易日总数 tradeday_pct: 交易日占比 :return: """ ap.sound(f'entry: stat_tradeday') self.tradeday_num = len([i for i in self.l_signal if i == 1]) self.tradeday_pct = self.tradeday_num / self.kline_num return self
def stat_yield(self): """ 统计收益率相关指标: l_each_strategy_pct: 每一笔交易的收益率 avg_strategy_pct: 总体平均收益率 profit_num: 盈利交易次数 profit_avg_pct: 盈利交易平均收益率 loss_num: 亏损交易次数 loss_avg_pct: 亏损交易平均收益率 win_pct: 胜率 winloss_pct: 盈亏比 strategy_pct: 策略总收益率 strategy_ytd_pct: 策略年化收益率 benchmark_pct: 基准总收益率 benchmark_ytd_pct: 基准年化收益率 car_pct: 超额收益率 max_each_strategy_pct: 策略单笔交易最大收益率 min_each_strategy_pct: 策略单笔交易最小收益率 :return: """ ap.sound(f'entry: stat_strategy') self.l_each_strategy_pct = [ sum(self.l_each_close_pct[x:y]) for (x, y) in self.l_holdidx ] self.avg_strategy_pct = float(np.mean(self.l_each_strategy_pct)) self.profit_num = len([i for i in self.l_each_strategy_pct if i > 0]) self.profit_avg_pct = float( np.mean([i for i in self.l_each_strategy_pct if i > 0])) self.loss_num = len([i for i in self.l_each_strategy_pct if i <= 0]) self.loss_avg_pct = float( np.mean([i for i in self.l_each_strategy_pct if i <= 0])) self.win_pct = self.profit_num / (self.profit_num + self.loss_num) self.winloss_pct = sum([ i for i in self.l_each_strategy_pct if i > 0 ]) / sum([i for i in self.l_each_strategy_pct if i <= 0]) self.strategy_pct = (self.l_cum_strategy_pct[-1] - 1) self.strategy_ytd_pct = qp.total_annualized_returns( self.strategy_pct, self.year_tradeday, self.kline_num) self.benchmark_pct = (self.df['close'][-1] - self.df['close'][0]) / self.df['close'][0] self.benchmark_ytd_pct = self.benchmark_pct / self.kline_num * self.year_tradeday self.car_pct = self.strategy_ytd_pct - self.benchmark_ytd_pct self.max_each_strategy_pct = max(self.l_cum_strategy_pct[1:]) self.min_each_strategy_pct = min(self.l_cum_strategy_pct[1:]) return self
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[1:])) self.mdd_pct = mdd['pct'] self.mdd_range = mdd['range'] mrg = qp.maxrevenuegrowth(np.array(self.l_cum_strategy_pct[1:])) self.mrg_pct = mrg['pct'] self.mrg_range = mrg['range'] return self
def output(self): ap.sound(f'entry: output') print(f'基准总收益率: {round(self.benchmark_pct, 4)}, 策略总收益率: {round(self.strategy_pct, 4)}\n' f'基准年化收益率: {round(self.benchmark_ytd_pct, 4)}, 策略年化收益率: {round(self.strategy_ytd_pct, 4)}\n' f'超额收益率: {round(self.car_pct, 4)}, 胜率: {round(self.win_pct, 4)}, 盈亏比: {round(self.winloss_pct, 4)}\n' f'夏普比率: {round(self.sharpe_pct, 4)}, Alpha: ..., Bate: ...\n' f'信息比率: ..., 策略波动率: {round(self.strategy_volatility, 4)}, ' f'基准波动率: {round(self.benchmark_volatility, 4)}' f'最高浮动收益率: {round(self.max_each_strategy_pct, 4)}, ' f'最低浮动收益率: {round(self.min_each_strategy_pct, 4)}\n' f'最大回撤率: {round(self.mdd_pct, 4)}, 最大回撤位置: {self.mdd_range}\n' f'最大收益率: {round(self.mrg_pct, 4)}, 最大收益位置: {self.mrg_range}\n' f'总交易次数: {len(self.l_each_strategy_pct)}, 平均收益率: {round(self.avg_strategy_pct, 4)}, ' f'平均持仓时间: {self.avg_holdtime}\n' f'盈利交易次数: {self.profit_num}, 盈利交易平均收益率: {round(self.profit_avg_pct, 4)}, ' f'盈利交易平均持仓时间: {self.profit_avg_holdtime}\n' f'亏损交易次数: {self.loss_num}, 亏损交易平均收益率: {round(self.loss_avg_pct, 4)}, ' f'亏损交易平均持仓时间: {self.loss_avg_holdtime}\n' f'总测试天数: {self.kline_num}, 持仓天数: {self.tradeday_num}, 持仓天数占比: {round(self.tradeday_pct, 2)}') return self
def stat_holdtime(self): """ 统计持仓时间相关指标: l_each_holdtime: 每一笔交易持仓时间 avg_holdtime: 总平均持仓时间 profit_avg_holdtime: 盈利交易平均持仓时间 loss_avg_holdtime: 亏损交易平均持仓时间 :return: """ ap.sound(f'entry: stat_holdtime') self.l_each_holdtime = [y - x for (x, y) in self.l_holdidx] self.avg_holdtime = np.mean(self.l_each_holdtime) arr_each_strategy = np.array(self.l_each_strategy_pct) arr_each_holdtime = np.array(self.l_each_holdtime) arr_profit = arr_each_holdtime[arr_each_strategy > 0] arr_loss = arr_each_holdtime[arr_each_strategy <= 0] self.profit_avg_holdtime = np.mean(arr_profit) self.loss_avg_holdtime = np.mean(arr_loss) return self
def asd_data(self): ap.sound(f'entry: asd_data') self.l_each_close_pct = self.df['close'].pct_change().tolist() self.l_cum_benchmark_pct = ( 1 + np.array(self.l_each_close_pct[1:])).cumprod() return self
def asd_data(self): ap.sound(f'entry: asd_data') self.l_each_close_pct = self.df['close'].pct_change().tolist() return self
def feedback(self): ap.sound(f'entry: feedback') df_feedback = pd.DataFrame() df_feedback['arr'] = self.l_signal df_feedback['arr_shift'] = self.l_signal df_feedback['arr_shift'] = df_feedback['arr_shift'].shift(1).fillna(0).astype(int) df_feedback['aux'] = df_feedback['arr'] - df_feedback['arr_shift'] df_feedback = df_feedback.reset_index() df_target = df_feedback[['aux']][df_feedback['aux'] != 0].reset_index().copy() print(f'df_target: {df_target.head()}') if df_target.loc[len(df_target) - 1, 'aux'] == 1: df_target = ap.concat(df_target, pd.DataFrame({'index': [df_target['index'].tolist()[-1] + 1], 'aux': [-1]})) l_aux = df_target['index'].tolist() step = 2 l_holdidx = [l_aux[i:i + step] for i in range(0, len(l_aux), step)] l_each_yield = [sum(self.l_each_close_pct[x:y]) for (x, y) in l_holdidx] avg_yield = float(np.mean(l_each_yield)) profit_num = len([i for i in l_each_yield if i > 0]) profit_avg_ratio = float(np.mean([i for i in l_each_yield if i > 0])) loss_num = len([i for i in l_each_yield if i < 0]) loss_avg_ratio = float(np.mean([i for i in l_each_yield if i < 0])) l_each_holdtime = [y - x for (x, y) in l_holdidx] avg_holdtime = np.mean(l_each_holdtime) arr_each_yield = np.array(l_each_yield) arr_each_holdtime = np.array(l_each_holdtime) arr_profit = arr_each_holdtime[arr_each_yield > 0] arr_loss = arr_each_holdtime[arr_each_yield < 0] profit_avg_holdtime = np.mean(arr_profit) loss_avg_holdtime = np.mean(arr_loss) mdd = qp.maxdrawdown(np.array(self.l_yield[1:])) mdd_ratio = mdd['ratio'] * 100 mdd_range = mdd['range'] mrg = qp.maxrevenuegrowth(np.array(self.l_yield[1:])) mrg_ratio = mrg['ratio'] * 100 mrg_range = mrg['range'] yields = (self.l_yield[-1] - 1) * 100 benchmark = (self.df['close'][-1] - self.df['close'][0]) / self.df['close'][0] * 100 max_yield = max(self.l_yield[1:]) * 100 min_yield = min(self.l_yield[1:]) * 100 # todo: 改成 (年化收益率 - 利率) / 标准差 sharpe_ratio = yields / 100 / np.std(self.l_yield, ddof=1) tradeday_num = len([i for i in self.l_signal if i == 1]) tradeday_ratio = tradeday_num / len(self.l_signal) * 100 print(f'基准收益率: {round(benchmark, 2)}%, 策略收益率: {round(yields, 2)}%, 夏普比率: {sharpe_ratio}\n' f'最高浮动收益率: {round(max_yield, 2)}%, 最低浮动收益率: {round(min_yield, 2)}%\n' f'最大回撤率: {round(mdd_ratio, 2)}%, 最大回撤位置: {mdd_range}\n' f'最大收益率: {round(mrg_ratio, 2)}%, 最大收益位置: {mrg_range}\n' f'总交易次数: {len(l_each_yield)}, 平均收益率: {round(avg_yield, 5)}%, 平均持仓时间: {avg_holdtime}\n' f'盈利交易次数: {profit_num}, 盈利交易平均收益率: {round(profit_avg_ratio, 5)}%, 盈利交易平均持仓时间: {profit_avg_holdtime}\n' f'亏损交易次数: {loss_num}, 亏损交易平均收益率: {round(loss_avg_ratio, 5)}%, 亏损交易平均持仓时间: {loss_avg_holdtime}\n' f'总测试天数: {len(self.l_signal)}, 持仓天数: {tradeday_num}, 持仓天数占比: {round(tradeday_ratio, 2)}%') dfs = pd.DataFrame() dfs['holdtime'] = l_each_holdtime dfs['yield'] = l_each_yield with sns.axes_style("dark"): # sns.jointplot('holdtime', 'yield', data=dfs, # kind='reg', color='grey', space=0, ratio=6, # marginal_kws={'bins': 20}, scatter={'s': 3}) sns.jointplot('holdtime', 'yield', data=dfs, kind='kde', color='grey', space=0, ratio=6) plt.show() return self
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_cum_strategy_pct = (1 + l_signal_pct).cumprod() return self