def test_annualized_return_rate(self): start = pd.Timestamp(equity.index[0]) end = pd.Timestamp(equity.index[-1]) logger.info('---start end---: {} {}'.format(start, end)) rate = analysis.annualized_return_rate(equity, capital, start, end) logger.info('---annualized_return_rate---: {}'.format(rate)) self.assertEqual(rate, -60.66)
def test_get_trade_bars(self): op = analysis._true_func lenth_bar1 = analysis._get_trade_bars(ohlc_data, trade_log, op) self.assertEquals(lenth_bar1, [19, 9, 3, 2, 8, 5]) logger.info('trade_log: ', trade_log) trade_log1 = trade_log.drop([0, 1]).reset_index(drop=True) logger.debug('----------------------------------') logger.debug('trade_log: ', trade_log1) lenth_bar2 = analysis._get_trade_bars(ohlc_data, trade_log1, op) self.assertEquals(lenth_bar2, [9, 3, 2, 8, 5])
def average_true_range(self, period: int) -> float: """period个周期内的平均真实波幅,一般称为ATR""" if not isinstance(period, int): logger.info('period must be int, please input int') high = self.high(period) # type:numpy.ndarray,可以直接进行列表计算 low = self.low(period) # type:numpy.ndarray last_open = self.open(period + 1)[:-1] # type:numpy.ndarray high_low = high - low # type:numpy.ndarray high_last_open = high - last_open last_open_low = last_open - low # true_range = max(high - low, abs(high - last_open), abs(last_open - low)) true_range = [ max(high_low[i], high_last_open[i], last_open_low[i]) for i in range(period) ] average_true_range = talib.SMA(np.array(true_range), period) return average_true_range[-1]
def min_low(self, period: int, index=0): """ 获取 period 个周期内的最低价, index 为 0 表示 period + 1 日前到当日的最低价, index 为 1 表示 period + 2 日前到昨日的最低价, +2 是因为要与上一个 index 的周期相同,便于比较, 用于计算 cross up 和 cross down """ if index not in [0, 1]: logger.warning( 'index must be 0 or 1, please choose the right index') logger.info('index set to 0 by default') index = 0 if not index: low = self.get_basic_data(period, ohlc='low') if index == 1: low = self.get_basic_data(period + 1, ohlc='low')[:-1] return min(low)
def max_high(self, period: int, index=0): """ 获取 period 个周期内的最高价, 1 表示当前周期,2 表示上一周期到当前周期,类推, index 为 0 表示 period - 1 日前到当日的最高价,即 period 个周期内的最高价, index 为 1 表示 period 日前到昨日的最高价,也是 period 个周期内的最高价, index 是为比较函数 cross_up 和 cross_down 设置的 """ logger.debug('period, index : {} {}'.format(period, index)) if index not in [0, 1]: logger.warning( 'index must be 0 or 1, please choose the right index') logger.info('index set to 0 by default') index = 0 if not index: high = self.get_basic_data(period, ohlc='high') if index == 1: high = self.get_basic_data(period + 1, ohlc='high')[:-1] return max(high)
def get_analysis(self, instrument): """输出详细的结果分析""" logger.info('-----get_analysis-----') ohlc_data = self.feed_list[0].bar.df ohlc_data = ohlc_data.drop(len(ohlc_data) - 1) # 最后一条数据为重复的数据 logger.debug('---feed_list---: {} {}'.format(self.feed_list, self.feed_list[0].bar)) ohlc_data.set_index('time', inplace=True) # 设立新索引 ohlc_data.index = pd.DatetimeIndex( ohlc_data.index) # 将新索引转换为 DatetimeIndex logger.debug('------ohlc_data.index------: {}'.format(ohlc_data.index)) logger.debug('------type of ohlc_data.index------: {}'.format( type(ohlc_data.index))) self.context.ohlc_data = ohlc_data # pd.DataFrame dbal = self.fill.equity.df # 权益 start = dbal.index[0] # 开始日期 end = dbal.index[-1] # 结束日期 logger.debug('----start----: {}'.format(start)) logger.debug('----end----: {}'.format(end)) logger.debug('----type of end----: {}'.format(type(end))) logger.debug('----context.start_date----: {}'.format( self.context.start_date)) logger.debug('----context.end_date----: {}'.format( self.context.end_date)) logger.debug('----type of context.end_date----: {}'.format( type(self.context.end_date))) # capital = self.fill.initial_cash # 初始资金 trade_log = self.get_trade_log(instrument) # logger.info('------trade_log-----: {}'.format(trade_log)) trade_log = trade_log[trade_log['lots'] != 0] trade_log.to_csv(date + '_trade_log.csv') self.context.trade_log = trade_log logger.info('------trade_log-----: {}'.format(trade_log)) logger.debug('------context.fill-----: {}'.format( self.context.fill.long_realized_gain_and_loss.df)) trade_log.reset_index(drop=True, inplace=True) analysis = stats(self.context) logger.info('----------------------stats-----------------------') logger.debug('---analysis_table---: {}'.format(analysis)) equity = self.context.fill.equity.df equity.to_csv(date + '_equity.csv') analysis_table = dict_to_table(analysis) with open(date + '_analysis_table.txt', 'w') as f: f.write(str(analysis_table)) logger.info('analysis_table: {}'.format(analysis_table))
def get_last_closed_equity(): """获取上一次开仓后的权益""" df = self.position.df logger.info('---type of df---: {} {}'.format(type(df), df)) closed_date = df[df == 0].dropna().index[-1] # 找到最近一个不持仓的时间 index_num = df.index.get_loc(closed_date) + 1 logger.info('---index_num---: {}'.format(index_num)) eq_df = self.equity.df logger.info('---type of df---: {} {}'.format(type(eq_df), eq_df)) index = eq_df.index[index_num] equity = eq_df[eq_df.index == index]['equity'][0] return equity
def __output_summary(self): """输出简略的回测结果""" total = pd.DataFrame(self.fill.equity.dict) logger.debug('-----------self.fill.equity-------------: {}'.format( self.fill.equity)) logger.debug('-------total---------: {}'.format(total)) # total.to_csv('total.csv') # equity = self.fill.equity.df # equity.to_csv('equity.csv') logger.debug('-----------total.index----------: {}'.format( total.index)) logger.debug('-----------total.columns----------: {}'.format( total.columns)) drawdown = create_drawdowns(total) logger.debug('----------------drawdown done----------') # 计算列中的后一个元素与前一个元素差的百分比 total.set_index('date', inplace=True) # 去掉 date,保留 equity pct_returns = total.pct_change() logger.debug('------------pct_change------------') total /= self.fill.initial_cash logger.debug('-----------total /--------------') # max_drawdown_pct, duration_for_pct = create_drawdowns(total['equity']) # logger.info('------------max_drawdown, duration----------: {} {}'.format(max_drawdown_pct, duration_for_pct)) # logger.info('------------max_drawdown, duration----------: {} {}'.format(max_drawdown_value, duration_for_value)) # 计算测试天数 date_type = '%Y-%m-%d' start_date = datetime.strptime(self.context.start_date, date_type) end_date = datetime.strptime(self.context.end_date, date_type) self.context.test_days = (end_date - start_date).days results = OrderedDict() results['测试开始时间'] = self.context.start_date results['测试结束时间'] = self.context.end_date results['测试天数'] = self.context.test_days # 从测试数据开始到结束的天数 results['测试周期'] = self.context.count # 测试数据的 K 线数 results['指令总数'] = len(self.fill.completed_list) # 指令总数 results['初始资金'] = self.fill.initial_cash results['最终权益'] = round(self.fill.equity[-1], 2) # 最终权益 logger.info('------------results-----------: {}'.format(results)) total_return = round(results['最终权益'] / self.fill.initial_cash - 1, 4) logger.info( '------------total_return-----------: {}'.format(total_return)) results['夏普比率'] = round(create_sharpe_ratio(pct_returns), 2) results['盈利率'] = str(round(total_return * 100, 2)) + '%' results['最大回撤'] = drawdown['drawdown'].max() results['最大回撤时间'] = drawdown['date'][ drawdown['drawdown'].idxmax()] # 只能找出一个 index results['最大回撤比'] = str(round(drawdown['pct'].max() * 100, 2)) + '%' results['最大回撤比时间'] = drawdown['date'][drawdown['pct'].idxmax()] logger.debug('----short_realized_gain_and_loss----: {}'.format( self.fill.short_realized_gain_and_loss.dict)) logger.debug( '---type of short_realized_gain_and_loss.list---: {}'.format( type(self.fill.short_realized_gain_and_loss.list))) logger.debug('---short_realized_gain_and_loss.list---: {}'.format( self.fill.short_realized_gain_and_loss.list)) long_profit = [ i for i in self.fill.long_realized_gain_and_loss.list if i >= 0 ] long_loss = [ i for i in self.fill.long_realized_gain_and_loss.list if i < 0 ] long_commission = self.fill.long_commission.list short_profit = [ i for i in self.fill.short_realized_gain_and_loss.list if i >= 0 ] short_loss = [ i for i in self.fill.short_realized_gain_and_loss.list if i < 0 ] short_commission = self.fill.short_commission.list logger.debug( '---self.fill.long_realized_gain_and_loss.list---: {}'.format( self.fill.long_realized_gain_and_loss.list)) logger.debug( '---self.fill.short_realized_gain_and_loss.list---: {}'.format( self.fill.short_realized_gain_and_loss.list)) logger.debug('----long_profit, long_loss---: {} {}'.format( long_profit, long_loss)) logger.debug('----long_commission---: {}'.format(long_commission)) logger.debug('----short_profit, short_loss---: {} {}'.format( short_profit, short_loss)) logger.debug('----short_commission---: {}'.format(short_commission)) results['多头总盈利'] = round(sum(long_profit), 2) results['多头总亏损'] = round(sum(long_loss), 2) results['空头总盈利'] = round(sum(short_profit), 2) results['空头总亏损'] = round(sum(short_loss), 2) results['手续费'] = sum(long_commission) + sum(short_commission) logger.info('---------results---------: {}'.format(results)) results_table = dict_to_table(results) with open(date + '_results.txt', 'w') as f: f.write(results_table)
def next(self): # ma2 = self.indicator.SMA(period=2, index=-1) # high = self.high # low = self.low # close = self.close # # true_range = max(high - low, abs(close.data(1) - high), abs(close.data(1) - low)) # # average_true_range = talib.SMA(true_range, 20) # average_true_range = self.average_true_range.data(5) # money = self.equity[-1] # units = self.units # trade_lots = int(money * 0.01 / (units * average_true_range)) + 1 # total_trade_lots = 4 * trade_lots # max_high = self.max_high.data(5) # min_low = self.min_low.data(5) # cross = self.cross # if cross.crossup(close, max_high): # self.buy(trade_lots) # if cross.crossdown(close, min_low): # self.sell(trade_lots) """后续再考虑变量的问题,先把策略写死,往后进行""" # high = indicator.Indicators(self.market_event, 'high') # logger.info('high.data_dict: {}'.format(high.data_dict)) # low = indicator.Indicators(self.market_event, 'low') # close = indicator.Indicators(self.market_event, 'close') # close_ref = indicator.Indicators(self.market_event, close, 1) # logger.info('close_ref and type of it: {} {}'.format(close_ref, type(close_ref))) # arg1 = high - low # value = indicator.Evaluate(arg1) # value = value.evaluate() # logger.info('value: {}'.format(value)) # logger.info('arg1 and type of it: {} {}'.format(arg1, type(arg1))) # logger.info('arg1.data_dict: {}'.format(arg1.data_dict)) # arg2 = abs(close_ref - high) # logger.info('arg2 and type of it: {} {}'.format(arg2, type(arg2))) # arg3 = abs(close_ref - low) # tr = indicator.Indicators.max(arg1, arg2, arg3) # logger.info('tr.data_dict: {}'.format(tr.data_dict)) # atr = indicator.Indicators.moving_average(tr, 5) # logger.info('self.equity: {}'.format(self.equity)) # logger.info('self.equity[-1]: {}'.format(self.equity[-1])) # money = self.equity[-1] # logger.info('money: {}'.format(money)) # unit = self.units # tc = indicator.Indicators.int_part(money * 0.01 / unit * atr) # mtc = tc * 4 # hh = indicator.Indicators.max_high(high, 5) # ll = indicator.Indicators.min_low(low, 5) # 引用技术指标基类,方便调用各种参数 indi = indicator.Indicator(self.market_event) money = indi.money() unit = indi.units() atr = indi.average_true_range(4) tc = int(money * 0.01 / (unit * atr)) mtc = 4 * tc # 列表中放入数据时,前面周期的数据放前面,当前周期的数据放后面 close = [] close.append(indi.close(2)[0]) # 前一周期的 close close.append(indi.close(1)[0]) # 当前周期的 close logger.info('close list: {}'.format(close)) max_high = [] max_high.append(indi.max_high(3, 1)) max_high.append(indi.max_high(3)) min_low = [] min_low.append(indi.min_low(3, 1)) min_low.append(indi.min_low(3)) up = indi.cross_up(close, max_high) logger.info('up上穿: {}'.format(up)) down = indi.cross_down(close, max_high) logger.info('down下穿: {}'.format(down))
def test_max_consecutive_winning_trades(self): number = analysis.max_consecutive_winning_trades(context) logger.info('---number---: {}'.format(number)) self.assertEqual(number, 1)
def test_risk_rate(self): risk_rate = round(analysis.risk_rate(equity) / 100, 4) logger.info('---risk_rate---: {}'.format(risk_rate)) self.assertEqual(risk_rate, 0.1798)
def test_sharpe_ratio(self): rets = equity['equity'].pct_change() # sharpe_ratio = analysis.sharpe_ratio(rets) sharpe_ratio = analysis.sharpe_ratio(rets, 500000, 28) logger.info('---sharpe_ratio---: {}'.format(sharpe_ratio)) self.assertEqual(sharpe_ratio, 1.0)
def update_time_index(self, feed_list): """ 保持每日开盘后的数据更新, 若当日无交易,则更新后的数据即为当日数据, 若当日有交易,则交易后的数据覆盖开盘后更新的数据 """ date = feed_list[-1].cur_bar.cur_date logger.info('---------------------------------------------------------------') logger.info('update_time_index, 开盘后date in backtestfill: {}'.format(date)) # for feed in feed_list: feed = feed_list[-1] # 控制计算的价格,同指令成交价一样 price = feed.cur_bar.cur_close # 取收盘价为计算价格 # high = feed.cur_bar.cur_high # low = feed.cur_bar.cur_low logger.info('price in date in update_time_index: {} {}'.format(price, date)) self.set_dataseries_instrument(feed.instrument) # self.position.copy_last(date) # 更新仓位 # logger.debug('self.position in backtestfill: {}'.format(self.position)) # logger.info('self.position in backtestfill: {}'.format(self.position[-1])) logger.debug('self.position[-1] in date in update_time_index: {} {}'.format(self.position[-1], date)) # 更新保证金 # margin = self.position[-1] * price * feed.per_margin * feed.mult # margin = feed.per_margin * feed.units * price * 300 margin = self.margin[-1] # 未持仓时,保证金 = 上一次开仓保证金 或 0(平仓后) logger.debug('feed.units: {}'.format(feed.units)) # margin = abs(self.position[-1]) * price * feed.per_margin logger.debug('margin in date in update_time_index: {} {}'.format(margin, date)) self.margin.add(date, margin) # 更新平均价格 # self.avg_price.copy_last(date) # 更新手续费,注意期货手续费需要重新计算 commission = 0 # 无交易进行时,手续费 = 0 self.commission.add(date, commission) logger.debug('commission in date in update_time_index: {} {}'.format(self.commission[-1], date)) # self.commission.add(date, commission) # 更新浮动盈亏 cur_avg = self.avg_price[-1] self.avg_price.add(date, cur_avg) cur_position = self.position[-1] self.position.add(date, cur_position) logger.debug('cur_position, cur_avg in date update_time_index: {} {} {}'.format(cur_position, cur_avg, date)) # 浮盈 = (卖出价 - 买入价)× 手数 # 若卖出平仓,则原仓位为正,浮盈 = (平仓价(卖出)- 持仓均价)× 原仓位 # 若买入平仓,则原仓位为负,浮盈 = (平仓价(买入)- 持仓均价)× 原仓位 unrealized_g_l = (price - cur_avg) * cur_position * feed.units if self.avg_price[-1] == 0: unrealized_g_l = 0 # unrealized_g_l = unrealized_g_l_high = unrealized_g_l_low = 0 logger.debug('cur_avg in date in update_time_index: {} {}'.format(cur_avg, date)) logger.debug('unrealized_g_l in date in update_time_index: {} {}'.format(unrealized_g_l, date)) self.unrealized_gain_and_loss.add(date, unrealized_g_l) def get_last_closed_equity(): """获取上一次开仓后的权益""" df = self.position.df logger.info('---type of df---: {} {}'.format(type(df), df)) closed_date = df[df == 0].dropna().index[-1] # 找到最近一个不持仓的时间 index_num = df.index.get_loc(closed_date) + 1 logger.info('---index_num---: {}'.format(index_num)) eq_df = self.equity.df logger.info('---type of df---: {} {}'.format(type(eq_df), eq_df)) index = eq_df.index[index_num] equity = eq_df[eq_df.index == index]['equity'][0] return equity # 更新equity # last_equity = get_last_closed_equity() total_re_profit = sum(self.realized_gain_and_loss.list) logger.debug('total_re_profit: {}'.format(total_re_profit)) logger.debug('self.realized_gain_and_loss.list: {}'.format(self.realized_gain_and_loss.list)) total_profit = total_re_profit + self.unrealized_gain_and_loss[-1] logger.debug('total_profit in date in update_time_index: {} {}'.format(total_profit, date)) logger.debug('sum(self.commission.list) in date in update_time_index: {} {}'.format( sum(self.commission.list), date)) # logger.debug('self.commission.list in date in update_time_index: {} {}'.format(self.commission.list, date)) total_commission = sum(self.commission.list) logger.debug('total_re_profit: {}'.format(total_re_profit)) logger.debug('total_commission:{}'.format(total_commission)) # 持仓时余额 = 上一次开仓后的余额 + 当日浮动盈亏 # equity = last_equity + unrealized_g_l # **这里可能有问题** equity = self.initial_cash + total_profit - total_commission # logger.info('equity, date in update_time_index: {} {} {}'.format(date, equity, equity2)) self.equity.add(date, equity) # 更新cash # total_margin = self.margin.total() logger.debug('total_margin in update_time_index: {}'.format(margin)) if self.position[-1] == 0: cash = self.equity[-1] else: cash = np.round(self.equity[-1] - margin, 2) logger.debug('cash in date in update_time_index: {} {}'.format(cash, date)) self.cash.add(date, cash) logger.info('####################################') # 检查是否爆仓 if self.equity[-1] <= 0 or self.cash[-1] <= 0: for feed in feed_list: feed.continue_backtest = False logger.info('警告:策略已造成爆仓!') logger.info('####################################')