def output_all_metrics_as_df(self, split=None, split_type=None): output_df = TimeSeriesSplit(self.data.copy()).split_selector( split, split_type) output_df['Trades'] = self._trade_count_series(split, split_type) output_df['EntryPrice'] = self._entry_price_series(split, split_type) output_df['PL'] = self._pnl_series(split, split_type) output_df['InTradePL_Accum'] = output_df['PL'].cumsum() output_df['ClosedPL'] = self.closed_trade_pnl_series(split, split_type) output_df['ClosedPL_Accum'] = output_df['ClosedPL'].cumsum() output_df['Closed_Pnl_CumSum'] = output_df['ClosedPL'].cumsum() output_df['Closed_Pnl_CumMax'] = output_df['Closed_Pnl_CumSum'].cummax( ) output_df['ClosedTradeDrawDown'] = output_df[ 'Closed_Pnl_CumSum'] - output_df['Closed_Pnl_CumMax'] output_df['MaxClosedDraw'] = self._closed_max_draw_series( split, split_type) output_df['Max'] = output_df['PL'].cummax() output_df['MaxDrawPerPeriod'] = np.where( output_df['Max'] != output_df['Max'].shift(-1), output_df['MaxClosedDraw'], 0) output_df['AverageWin'] = self._average_win_series(split, split_type) output_df['AverageLoss'] = self._average_loss_series(split, split_type) output_df.drop(['Max', 'Closed_Pnl_CumSum', 'Closed_Pnl_CumMax'], axis=1, inplace=True) return output_df
def _trade_count_series(self, split=None, split_type=None): trade_series = TimeSeriesSplit(self.data).split_selector( split, split_type) trade_series['Trades'] = np.logical_and( trade_series['Position'] != trade_series['Position'].shift(1), trade_series['Position'] != 0).cumsum() trade_series.loc[trade_series['Position'] == 0, 'Trades'] = 0 return trade_series['Trades']
def _closed_max_draw_series(self, split=None, split_type=None): max_draw = TimeSeriesSplit(self.data).split_selector(split, split_type) max_draw['PL_Accum'] = self._pnl_series(split, split_type) max_draw['Max'] = max_draw['PL_Accum'].cummax() max_draw['Diff'] = max_draw['PL_Accum'] - max_draw['Max'] max_draw['PeriodDraw_Closed'] = max_draw.groupby(max_draw['Diff'].eq(0).cumsum())['PL_Accum'] \ .transform(lambda x: x.cummin()) - max_draw['Max'] return max_draw['PeriodDraw_Closed']
def short_trades(self, split=None, split_type=None): shorts = TimeSeriesSplit(self.data.copy()).split_selector( split, split_type) shorts = np.logical_and( shorts['Position'] == -1, np.logical_not(shorts['Position'].shift(1) == -1)).sum() return shorts
def long_trades(self, split=None, split_type=None): longs = TimeSeriesSplit(self.data.copy()).split_selector( split, split_type) longs = np.logical_and( longs['Position'] == 1, np.logical_not(longs['Position'].shift(1) == 1)).sum() return longs
def __init__(self, data: pd.DataFrame, position_logic_range: PositionLogicRange, split: Tuple[int, int], timeseries_split: Tuple[str, str] = None, timeseries_split_type: str = None, tick_size=.01, tick_value=10, lot_size=5): self.data = data self.no_split = split[0] self.initial_split_multiple = split[1] self.time_series_split = timeseries_split self.time_series_split_type = timeseries_split_type self.position_logic_range = position_logic_range self.position_logic_combos = list( position_logic_range.position_logic_generator()) self.tick_size = tick_size self.tick_value = tick_value self.lot_size = lot_size if split is None: pass else: self.data = TimeSeriesSplit(self.data).split_selector( self.time_series_split, self.time_series_split_type)
def tabular_trades(self, group_by_col='Trades', split=None, split_type=None): grouping_df = TimeSeriesSplit(self.data).split_selector( split, split_type) grouping_df['Trades'] = self._trade_count_series() grouping_df['EntryPrice'] = self._entry_price_series() compact_results = grouping_df.groupby([group_by_col]).apply( self._grouping_function) compact_results['PL'] = (compact_results['ExitPrice'] - compact_results['EntryPrice']) * compact_results['TradeType']\ /self.tick_size * self.tick_value * self.lot_size compact_results['TotPL'] = compact_results['PL'].cumsum() compact_results['Max'] = compact_results['TotPL'].cummax() compact_results[ 'Diff'] = compact_results['TotPL'] - compact_results['Max'] df = compact_results.reset_index() m = [] for i in df.index: if df.iloc[i, df.columns.get_loc('TotPL')] == df.iloc[ i, df.columns.get_loc('Max')]: m.append(df.iloc[i, df.columns.get_loc('Diff')]) elif i == 0: m.append(0) else: m.append(min(m[i - 1], df.iloc[i, df.columns.get_loc('Diff')])) compact_results['PeriodDraw'] = m compact_results['MaxDraw'] = compact_results['PeriodDraw'].cummin() compact_results['PMD'] = compact_results['TotPL'] / compact_results[ 'MaxDraw'].abs() compact_results['PercentWin'] = ((compact_results['PL'] > 0).cumsum() / compact_results.index.values) * 100 compact_results['Day'] = compact_results['EntryTime'].dt.day compact_results['Month'] = compact_results['EntryTime'].dt.month compact_results['Year'] = compact_results['EntryTime'].dt.year return compact_results
def walk_forward(self, optimzation_window_type='RollingOptimizationWindow'): optimization_results = {} walk_forward_results = {} for e, position_logic in enumerate(self.position_logic_combos): start = time.time() param_list = tuple(signal_function.function_parameters_tuple for signal_function in position_logic.signal_func_list)\ + position_logic.entry.function_parameters_tuple + position_logic.exit.function_parameters_tuple backtest_instance = Backtest(self.data, position_logic, tick_size=self.tick_size, tick_value=self.tick_value, lot_size=self.lot_size) opt_loop_results = {} wf_loop_results = {} for i in range(1, self.no_split + 1): opt_loop_results[i] = backtest_instance.profit_to_max_draw( split=(self.no_split, self.initial_split_multiple, i), split_type=optimzation_window_type) wf_loop_results[i] = backtest_instance.agg_stats( split=(self.no_split, self.initial_split_multiple, i), split_type='WalkForwardWindow') print(f"Finished window {i} of {self.no_split}") optimization_results[str(param_list)] = opt_loop_results walk_forward_results[str(param_list)] = wf_loop_results end = time.time() print(f"Run {e+1}/{len(self.position_logic_combos)} ") print( f"{round(((len(self.position_logic_combos)-e+1)*(end-start))/60,2)}min Remaining" ) print(round(end - start, 3)) opt_df = pd.DataFrame.from_dict(optimization_results, orient='index') wf_df = pd.DataFrame.from_dict(walk_forward_results, orient='index') testdict = {} for i in opt_df.columns: test_dates = TimeSeriesSplit(self.data).walk_forward_dates( (self.no_split, self.initial_split_multiple, i)) top_param = opt_df[i].idxmax(axis=0) dict_val = wf_df.at[top_param, i] testdict[i] = dict_val testdict[i]['Params'] = top_param testdict[i]['WindowStart'] = test_dates[0] testdict[i]['WindowEnd'] = test_dates[1] final_df = pd.DataFrame.from_dict( testdict, orient='index') # This should be outside the loop return final_df
def _pnl_series(self, split=None, split_type=None): pnl_series = TimeSeriesSplit(self.data).split_selector( split, split_type) if self.price == 'O': pnl_series['TickChange'] = pnl_series['O'] - pnl_series['O'].shift( 1) elif self.price == 'C': pnl_series['TickChange'] = pnl_series['C'] - pnl_series['C'].shift( 1) pnl_series['PL'] = ( pnl_series['Position'] * pnl_series['TickChange'] ) / self.tick_size * self.tick_value * self.lot_size pnl_series['PL_Accum'] = pnl_series['PL'].cumsum() return pnl_series['PL_Accum']
def __init__(self, data, position_logic_function, split: Tuple[any] = None, split_type: str = None, process_data=True, initial_split_method=None, sort_data: str = True, price='O', tick_size=.01, tick_value=10, lot_size=5): self.data = data self.process_data = process_data self.position_logic_function = position_logic_function self.price = price self.split = split self.split_type = split_type self.sort_data = sort_data self.tick_size = tick_size self.tick_value = tick_value self.lot_size = lot_size if np.logical_or( np.logical_and(self.split is None, initial_split_method is not None), np.logical_and(self.split is not None, initial_split_method is None)): raise ValueError('Please provide Initial Split Type') if sort_data: if sorted(self.data.index) is not self.data.index: self.data.sort_index(ascending=True, inplace=True) if self.process_data: df = self.data.copy() if split_type is None: df = position_logic_function.apply(df) df['Position'] = list(position_handler(df)) else: if initial_split_method == 'PreSignalCalc': df = TimeSeriesSplit(df).split_selector(split, split_type) df = position_logic_function.apply(df) df['Position'] = list(position_handler(df)) elif initial_split_method == 'PostSignalCalc': df = position_logic_function.apply(df) df = TimeSeriesSplit(df).split_selector(split, split_type) df['Position'] = list(position_handler(df)) elif initial_split_method is None: pass df.drop(columns=['EntrySignal', 'ExitSignal'], inplace=True) self.data = df elif not self.process_data: self.data = self.data.copy()
def _entry_price_series(self, split=None, split_type=None): entry_price = TimeSeriesSplit(self.data).split_selector( split, split_type) if self.price == 'O': entry_price['EntryPrice'] = np.where( np.logical_and((entry_price['Position'] != entry_price['Position'].shift(1)), entry_price['Position'] != 0), entry_price['O'].shift(1), 0) elif self.price == 'C': entry_price['EntryPrice'] = np.where( np.logical_and((entry_price['Position'] != entry_price['Position'].shift(1)), entry_price['Position'] != 0), entry_price['C'].shift(1), 0) return entry_price['EntryPrice']
def closed_trade_pnl_series(self, split=None, split_type=None): closed_trade_pnl = TimeSeriesSplit(self.data).split_selector( split, split_type) closed_trade_pnl['PL'] = self._pnl_series(split, split_type) closed_trade_pnl['P&L Values'] = np.where( closed_trade_pnl['Position'] - closed_trade_pnl['Position'].shift(-1) != 0, closed_trade_pnl['PL'], np.nan) closed_trade_pnl['P&L Values'] = closed_trade_pnl['P&L Values'].fillna( method='ffill') closed_trade_pnl['Closed P&L'] = np.where( closed_trade_pnl['Position'] - closed_trade_pnl['Position'].shift(-1) != 0, closed_trade_pnl['P&L Values'] - closed_trade_pnl['P&L Values'].shift(1), 0) return closed_trade_pnl['Closed P&L']
def open_max_draw(self, split=None, split_type=None): open_draw = TimeSeriesSplit(self.data.copy()).split_selector( split, split_type) open_draw['Trades'] = self._trade_count_series(split, split_type) conditions = [open_draw['Position'] == 1, open_draw['Position'] == -1] long = open_draw.groupby('Trades')['L'].cummin() - open_draw.groupby( 'Trades')['EntryPrice'].cummax() short = open_draw.groupby('Trades')['EntryPrice'].cummax( ) - open_draw.groupby('Trades')['H'].cummax() values = [long, short] open_draw['MaxOpenDraw_Pd'] = np.select(conditions, values, 0) return open_draw['MaxOpenDraw_Pd'].min( ) / self.tick_size * self.lot_size * self.tick_value
def __init__(self, data, split=None, split_type=None, tick_size=.01, tick_value=10, lot_size=5): self.data = data self.split = split self.split_type = split_type self.tick_size = tick_size self.tick_value = tick_value self.lot_size = lot_size if isinstance(self.data, Backtest): self.data = data.data else: if split is None: pass else: self.data = TimeSeriesSplit(self.data).split_selector( self.split, self.split_type)