def test_beta(self): res_a = empyrical.beta(ret['a'], benchmark_rets['a']) res_b = empyrical.beta(ret['b'], benchmark_rets['b']) res_c = empyrical.beta(ret['c'], benchmark_rets['c']) assert isclose(ret['a'].vbt.returns.beta(benchmark_rets['a']), res_a) pd.testing.assert_series_equal( ret.vbt.returns.beta(benchmark_rets), pd.Series([res_a, res_b, res_c], index=ret.columns))
def test_beta(self): res_a = empyrical.beta(ret['a'], benchmark_rets['a']) res_b = empyrical.beta(ret['b'], benchmark_rets['b']) res_c = empyrical.beta(ret['c'], benchmark_rets['c']) assert isclose(ret['a'].vbt.returns.beta(benchmark_rets['a']), res_a) pd.testing.assert_series_equal( ret.vbt.returns.beta(benchmark_rets), pd.Series([res_a, res_b, res_c], index=ret.columns).rename('beta')) pd.testing.assert_series_equal( ret.vbt.returns.rolling_beta(ret.shape[0], benchmark_rets, minp=1).iloc[-1], pd.Series([res_a, res_b, res_c], index=ret.columns).rename(ret.index[-1]))
def comp_analysis(start_date='2017-07-01'): ''' 分析区间内基金公司收益率、波动率、beta与仓位变化 ''' comp_ret = pd.read_excel('%s/comp_ret.xlsx' % (const.FOF_DIR), index_col=0) comp_pos = pd.read_excel('%s/comp_position.xlsx' % (const.FOF_DIR), index_col=0) wdf = pd.read_csv('%s/881001.WI.csv' % (const.INDEX_DIR), index_col=1) wseries = wdf.pct_change()[wdf.index >= start_date]['close'] wseries.index = pd.to_datetime(wseries.index) df = pd.DataFrame(index=comp_ret.columns, columns=['ret', 'vol', 'beta', 'pos']) for c in df.index: series = comp_ret[c] series = series[series.index >= start_date] df.loc[c, 'ret'] = (1 + series).cumprod()[-1] - 1 df.loc[c, 'vol'] = empyrical.annual_volatility(series) wseries = wseries[(wseries.index >= series.index[0]) & (wseries.index <= series.index[-1])] df.loc[c, 'beta'] = empyrical.beta(series, wseries) if comp_pos[c].shape[0] > 1 and comp_pos[c][-2] != 0: df.loc[c, 'pos'] = (comp_pos[c][-1] - comp_pos[c][-2]) / comp_pos[c][-2] df = df.sort_values('ret', ascending=False) df = df.dropna() df.to_excel('%s/comp_analysis.xlsx' % (const.FOF_DIR))
def get_perf_att(series, bnchmark, rf=0.03 / 12, freq='monthly'): """F: that provides performance statistic of the returns params ------- series: daily or monthly returns returns: dataframe of Strategy name and statistics""" port_mean, port_std, port_sr = (get_stats(series, dtime=freq)) perf = pd.Series( { 'Annualized_Mean': '{:,.2f}'.format(round(port_mean, 3)), 'Annualized_Volatility': round(port_std, 3), 'Sharpe Ratio': round(port_sr, 3), 'Calmar Ratio': round(empyrical.calmar_ratio(series, period=freq), 3), 'Alpha': round(empyrical.alpha(series, bnchmark, risk_free=rf, period=freq), 3), 'Beta': round(empyrical.beta(series, bnchmark), 3), 'Max Drawdown': '{:,.2%}'.format(drawdown(series, ret_='nottext')), 'Sortino Ratio': round( empyrical.sortino_ratio( series, required_return=rf, period=freq), 3), }, ) perf.name = series.name return perf.to_frame()
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 perf_stats(returns, factor_returns=None, positions=None, transactions=None, turnover_denom='AGB'): """ Calculates various performance metrics of a strategy, for use in plotting.show_perf_stats. Parameters ---------- returns : pd.Series Daily returns of the strategy, noncumulative. - See full explanation in tears.create_full_tear_sheet. factor_returns : pd.Series, optional Daily noncumulative returns of the benchmark factor to which betas are computed. Usually a benchmark such as market returns. - This is in the same style as returns. - If None, do not compute alpha, beta, and information ratio. positions : pd.DataFrame Daily net position values. - See full explanation in tears.create_full_tear_sheet. transactions : pd.DataFrame Prices and amounts of executed trades. One row per trade. - See full explanation in tears.create_full_tear_sheet. turnover_denom : str Either AGB or portfolio_value, default AGB. - See full explanation in txn.get_turnover. Returns ------- pd.Series Performance metrics. """ stats = pd.Series() for stat_func in SIMPLE_STAT_FUNCS: stats[STAT_FUNC_NAMES[stat_func.__name__]] = stat_func( returns, annualization=APPROX_BDAYS_PER_YEAR) for stat_func in SIMPLE_STAT_FUNCS1: stats[STAT_FUNC_NAMES1[stat_func.__name__]] = stat_func(returns) if positions is not None: stats['Gross leverage'] = gross_lev(positions).mean() if transactions is not None: stats['Daily turnover'] = get_turnover(positions, transactions, turnover_denom).mean() if factor_returns is not None: stats['Alpha'] = ep.alpha(returns, factor_returns, annualization=APPROX_BDAYS_PER_YEAR) stats['Beta'] = ep.beta(returns, factor_returns) # for stat_func in FACTOR_STAT_FUNCS: # res = stat_func(returns, factor_returns, annualization=APPROX_BDAYS_PER_YEAR) # stats[STAT_FUNC_NAMES[stat_func.__name__]] = res # for stat_func in FACTOR_STAT_FUNCS1: # res = stat_func(returns, factor_returns) # stats[STAT_FUNC_NAMES1[stat_func.__name__]] = res return stats
def beta(close, benchmark_close, risk_free=0.0): try: rets = daily_returns(close) benchmark_rets = daily_returns(benchmark_close) beta_data = empyrical.beta(rets, benchmark_rets, risk_free=risk_free) return beta_data except Exception as e: raise (e)
def calculate_metrics(self): self.benchmark_period_returns = \ cum_returns(self.benchmark_returns).iloc[-1] self.algorithm_period_returns = \ cum_returns(self.algorithm_returns).iloc[-1] if not self.algorithm_returns.index.equals( self.benchmark_returns.index): message = "Mismatch between benchmark_returns ({bm_count}) and \ algorithm_returns ({algo_count}) in range {start} : {end}" message = message.format(bm_count=len(self.benchmark_returns), algo_count=len(self.algorithm_returns), start=self._start_session, end=self._end_session) raise Exception(message) self.num_trading_days = len(self.benchmark_returns) self.trading_day_counts = pd.stats.moments.rolling_count( self.algorithm_returns, self.num_trading_days) self.mean_algorithm_returns = \ self.algorithm_returns.cumsum() / self.trading_day_counts self.benchmark_volatility = annual_volatility(self.benchmark_returns) self.algorithm_volatility = annual_volatility(self.algorithm_returns) self.treasury_period_return = choose_treasury( self.treasury_curves, self._start_session, self._end_session, self.trading_calendar, ) self.sharpe = sharpe_ratio(self.algorithm_returns, ) # The consumer currently expects a 0.0 value for sharpe in period, # this differs from cumulative which was np.nan. # When factoring out the sharpe_ratio, the different return types # were collapsed into `np.nan`. # TODO: Either fix consumer to accept `np.nan` or make the # `sharpe_ratio` return type configurable. # In the meantime, convert nan values to 0.0 if pd.isnull(self.sharpe): self.sharpe = 0.0 self.downside_risk = downside_risk(self.algorithm_returns) self.sortino = sortino_ratio(self.algorithm_returns, _downside_risk=self.downside_risk) self.information = information_ratio(self.algorithm_returns, self.benchmark_returns) self.beta = beta(self.algorithm_returns, self.benchmark_returns) self.alpha = alpha(self.algorithm_returns, self.benchmark_returns, _beta=self.beta) self.excess_return = self.algorithm_period_returns - \ self.treasury_period_return self.max_drawdown = max_drawdown(self.algorithm_returns) self.max_leverage = self.calculate_max_leverage()
def test_alpha_beta_equality(self, returns, benchmark): alpha_beta = empyrical.alpha_beta(returns, benchmark) assert_almost_equal( alpha_beta[0], empyrical.alpha(returns, benchmark), DECIMAL_PLACES) assert_almost_equal( alpha_beta[1], empyrical.beta(returns, benchmark), DECIMAL_PLACES)
def beta(daily_returns, benchmark_daily_returns, risk_free=0.0): """Beta""" try: logger.info('Calculating Beta...') check_inputs_length(daily_returns, benchmark_daily_returns) beta_data = empyrical.beta(daily_returns, benchmark_daily_returns, risk_free=risk_free) return beta_data except Exception as exception: raise exception
def _get_backtest_performance_metrics(ret, benchmark_ret): metrics = { 'alpha': empyrical.alpha(ret, benchmark_ret), 'beta': empyrical.beta(ret, benchmark_ret), 'return': empyrical.cum_returns_final(ret), 'cagr': empyrical.cagr(ret), 'sharpe': empyrical.sharpe_ratio(ret), 'max_drawdown': empyrical.max_drawdown(ret), 'var': empyrical.value_at_risk(ret), 'volatility': empyrical.annual_volatility(ret), } return metrics
def test_beta(self, returns, benchmark, expected): observed = empyrical.beta(returns, benchmark) assert_almost_equal(observed, expected, DECIMAL_PLACES) if len(returns) == len(benchmark): # Compare to scipy linregress returns_arr = returns.values benchmark_arr = benchmark.values mask = ~np.isnan(returns_arr) & ~np.isnan(benchmark_arr) slope, intercept, _, _, _ = stats.linregress( benchmark_arr[mask], returns_arr[mask]) assert_almost_equal(observed, slope)
def test_alpha_beta_equality(self, returns, benchmark): alpha_beta = empyrical.alpha_beta(returns, benchmark) assert_almost_equal(alpha_beta[0], empyrical.alpha(returns, benchmark), DECIMAL_PLACES) assert_almost_equal(alpha_beta[1], empyrical.beta(returns, benchmark), DECIMAL_PLACES) if len(returns) == len(benchmark): # Compare to scipy linregress returns_arr = returns.values benchmark_arr = benchmark.values mask = ~np.isnan(returns_arr) & ~np.isnan(benchmark_arr) slope, intercept, _, _, _ = stats.linregress( returns_arr[mask], benchmark_arr[mask]) assert_almost_equal(alpha_beta[0], intercept) assert_almost_equal(alpha_beta[1], slope)
def rolling_beta(returns, factor_returns, rolling_window=APPROX_BDAYS_PER_MONTH * 6): """ Determines the rolling beta of a strategy. Parameters ---------- returns : pd.Series Daily returns of the strategy, noncumulative. - See full explanation in tears.create_full_tear_sheet. factor_returns : pd.Series or pd.DataFrame Daily noncumulative returns of the benchmark factor to which betas are computed. Usually a benchmark such as market returns. - If DataFrame is passed, computes rolling beta for each column. - This is in the same style as returns. rolling_window : int, optional The size of the rolling window, in days, over which to compute beta (default 6 months). Returns ------- pd.Series Rolling beta. Note ----- See https://en.wikipedia.org/wiki/Beta_(finance) for more details. """ if factor_returns.ndim > 1: # Apply column-wise return factor_returns.apply(partial(rolling_beta, returns), rolling_window=rolling_window) else: out = pd.Series(index=returns.index) for beg, end in zip(returns.index[0:-rolling_window], returns.index[rolling_window:]): out.loc[end] = ep.beta( returns.loc[beg:end], factor_returns.loc[beg:end]) return out
def rolling_beta(returns, factor_returns, rolling_window=APPROX_BDAYS_PER_MONTH * 6): """ Determines the rolling beta of a strategy. Parameters ---------- returns : pd.Series Daily returns of the strategy, noncumulative. - See full explanation in tears.create_full_tear_sheet. factor_returns : pd.Series or pd.DataFrame Daily noncumulative returns of the benchmark factor to which betas are computed. Usually a benchmark such as market returns. - If DataFrame is passed, computes rolling beta for each column. - This is in the same style as returns. rolling_window : int, optional The size of the rolling window, in days, over which to compute beta (default 6 months). Returns ------- pd.Series Rolling beta. Note ----- See https://en.wikipedia.org/wiki/Beta_(finance) for more details. """ if factor_returns.ndim > 1: # Apply column-wise return factor_returns.apply(partial(rolling_beta, returns), rolling_window=rolling_window) else: out = pd.Series(index=returns.index) for beg, end in zip(returns.index[0:-rolling_window], returns.index[rolling_window:]): out.loc[end] = ep.beta(returns.loc[beg:end], factor_returns.loc[beg:end]) return out
def beta(returns, factor_returns): """Calculates beta. Parameters ---------- returns : pd.Series Daily returns of the strategy, noncumulative. - See full explanation in :func:`~pyfolio.timeseries.cum_returns`. factor_returns : pd.Series Daily noncumulative returns of the factor to which beta is computed. Usually a benchmark such as the market. - This is in the same style as returns. Returns ------- float Beta. """ return empyrical.beta(returns, factor_returns)
def beta(returns, factor_returns): """ Calculates beta. Parameters ---------- returns : pd.Series Daily returns of the strategy, noncumulative. - See full explanation in :func:`~pyfolio.timeseries.cum_returns`. factor_returns : pd.Series Daily noncumulative returns of the factor to which beta is computed. Usually a benchmark such as the market. - This is in the same style as returns. Returns ------- float Beta. """ return empyrical.beta(returns, factor_returns)
def compute(self, today, assets, out, x): ################ self.i = self.i + 1 if self.i % 21 == 1: nav = pd.DataFrame(x) y = nav.pct_change(self.time) ben_nav = pd.DataFrame(x[:, assets == self.index_sid]) valid_regression_r1 = ben_nav.pct_change( self.time) - 0.025 * 5.0 / 25 x1 = valid_regression_r1.loc[y.index] wbeta = pd.Series(np.zeros(y.shape[1])).astype(float) for j in range(0, y.shape[1]): temp = y.iloc[:, j] if np.isnan(temp[self.time:]).any(): wbeta[y.columns[j]] = np.nan else: yy = temp[~temp.isnull()] xx = x1.iloc[yy.index, 0] wbeta[y.columns[j]] = beta(yy, xx, risk_free=self.rf) out[:] = wbeta else: out[:] = np.nan
bm_returns = benchmark_rets.daily_performance['returns'] bm_positions = benchmark_rets.pyfolio_positions bm_transactions = benchmark_rets.pyfolio_transactions # ### Use Algo from Leverage Lecture # Use same algo as in the Leverage Lecture! bt = get_backtest('5986b969dbab994fa4264696') bt_returns = bt.daily_performance['returns'] bt_positions = bt.pyfolio_positions bt_transactions = bt.pyfolio_transactions bt_returns.plot() plt.savefig(PATH + 'rets.png', dpi=300) plt.close() print(empyrical.beta(bt_returns, bm_returns)) # # PyFolio Plots benchmark_rets = bm_returns # Cumulative Returns plt.subplot(2, 1, 1) pf.plotting.plot_rolling_returns(bt_returns, benchmark_rets) # Daily, Non-Cumulative Returns plt.subplot(2, 1, 2) pf.plotting.plot_returns(bt_returns) plt.tight_layout() plt.savefig(PATH + 'daily_and_cum_rets.png', dpi=300) plt.close()
def Strategy_performance(returns: pd.DataFrame, mark_benchmark: str = 'benchmark', periods: str = 'daily') -> pd.DataFrame: ''' 风险指标计算 returns:index-date col-数据字段 mark_benchmark:用于指明基准 periods:频率 ''' df: pd.DataFrame = pd.DataFrame() df['年化收益率'] = ep.annual_return(returns, period=periods) df['累计收益'] = returns.apply(lambda x: ep.cum_returns(x).iloc[-1]) df['波动率'] = returns.apply( lambda x: ep.annual_volatility(x, period=periods)) df['夏普'] = returns.apply(ep.sharpe_ratio, period=periods) df['最大回撤'] = returns.apply(lambda x: ep.max_drawdown(x)) df['索提诺比率'] = returns.apply(lambda x: ep.sortino_ratio(x, period=periods)) df['Calmar'] = returns.apply(lambda x: ep.calmar_ratio(x, period=periods)) # 相对指标计算 if mark_benchmark in returns.columns: select_col = [col for col in returns.columns if col != mark_benchmark] df['IR'] = returns[select_col].apply( lambda x: information_ratio(x, returns[mark_benchmark])) df['Alpha'] = returns[select_col].apply( lambda x: ep.alpha(x, returns[mark_benchmark], period=periods)) df['Beta'] = returns[select_col].apply( lambda x: ep.beta(x, returns[mark_benchmark])) # 计算相对年化波动率 df['超额收益率'] = df['年化收益率'] - \ df.loc[mark_benchmark, '年化收益率'] return df.T # def show_worst_drawdown_periods(returns: pd.Series, # benchmark_code: str = "000300.SH", # top: int = 5): # """ # Prints information about the worst drawdown periods. # Prints peak dates, valley dates, recovery dates, and net # drawdowns. # Parameters # ---------- # returns : pd.Series # Daily returns of the strategy, noncumulative. # - See full explanation in tears.create_full_tear_sheet. # top : int, optional # Amount of top drawdowns periods to plot (default 5). # """ # drawdown_df = ts.gen_drawdown_table(returns, top=top) # drawdown_df.index = list(range(1, len(drawdown_df) + 1)) # phase_change = compare_phase_change(returns, benchmark_code, top) # df = pd.concat((drawdown_df, phase_change), axis=1) # # print_table( # # df.sort_values('区间最大回撤 %', ascending=False), # # name='序号', # # float_format='{0:.2f}'.format, # # ) # return df # def compare_phase_change(returns: pd.Series, # benchmark_code: str, # top: int = 5) -> pd.DataFrame: # ''' # 对比策略与基准在回撤区间内的收益 # ------ # returns:策略净值收益率 # benchmark_code:基准的代码 # ''' # beginDt = returns.index.min() # endDt = returns.index.max() # benchmark = get_wsd_data(benchmark_code, # 'pct_chg', # beginDt, # endDt, # 'priceAdj=B', # usedf=True) # benchmark = benchmark['PCT_CHG'] / 100 # df = pd.DataFrame(columns=['策略收益%', '基准收益%'], # index=list(range(1, top + 1))) # drawdowns_list = ts.get_top_drawdowns(returns, top=top) # for i, v in enumerate(drawdowns_list): # peak_date, _, recovery_date = v # if pd.isnull(recovery_date): # df.loc[i + 1, '策略收益%'] = np.nan # df.loc[i + 1, '基准收益'] = np.nan # else: # df.loc[i + 1, '策略收益%'] = ep.cum_returns( # returns.loc[peak_date:recovery_date]).iloc[-1] # df.loc[i + 1, '基准收益%'] = ep.cum_returns( # benchmark.loc[peak_date:recovery_date])[-1] # return df
def update(self, dt, algorithm_returns, benchmark_returns, leverage): # Keep track of latest dt for use in to_dict and other methods # that report current state. self.latest_dt = dt dt_loc = self.cont_index.get_loc(dt) self.latest_dt_loc = dt_loc self.algorithm_returns_cont[dt_loc] = algorithm_returns self.algorithm_returns = self.algorithm_returns_cont[:dt_loc + 1] algorithm_returns_series = pd.Series(self.algorithm_returns) self.num_trading_days = len(self.algorithm_returns) if self.create_first_day_stats: if len(self.algorithm_returns) == 1: self.algorithm_returns = np.append(0.0, self.algorithm_returns) self.algorithm_cumulative_returns[dt_loc] = cum_returns( algorithm_returns_series).iloc[-1] algo_cumulative_returns_to_date = \ self.algorithm_cumulative_returns[:dt_loc + 1] self.mean_returns_cont[dt_loc] = \ algo_cumulative_returns_to_date[dt_loc] / self.num_trading_days self.mean_returns = self.mean_returns_cont[:dt_loc + 1] self.annualized_mean_returns_cont[dt_loc] = \ self.mean_returns_cont[dt_loc] * 252 self.annualized_mean_returns = \ self.annualized_mean_returns_cont[:dt_loc + 1] if self.create_first_day_stats: if len(self.mean_returns) == 1: self.mean_returns = np.append(0.0, self.mean_returns) self.annualized_mean_returns = np.append( 0.0, self.annualized_mean_returns) self.benchmark_returns_cont[dt_loc] = benchmark_returns self.benchmark_returns = self.benchmark_returns_cont[:dt_loc + 1] benchmark_returns_series = pd.Series(self.benchmark_returns) if self.create_first_day_stats: if len(self.benchmark_returns) == 1: self.benchmark_returns = np.append(0.0, self.benchmark_returns) self.benchmark_cumulative_returns[dt_loc] = cum_returns( benchmark_returns_series).iloc[-1] benchmark_cumulative_returns_to_date = \ self.benchmark_cumulative_returns[:dt_loc + 1] self.mean_benchmark_returns_cont[dt_loc] = \ benchmark_cumulative_returns_to_date[dt_loc] / \ self.num_trading_days self.mean_benchmark_returns = self.mean_benchmark_returns_cont[:dt_loc] self.annualized_mean_benchmark_returns_cont[dt_loc] = \ self.mean_benchmark_returns_cont[dt_loc] * 252 self.annualized_mean_benchmark_returns = \ self.annualized_mean_benchmark_returns_cont[:dt_loc + 1] self.algorithm_cumulative_leverages_cont[dt_loc] = leverage self.algorithm_cumulative_leverages = \ self.algorithm_cumulative_leverages_cont[:dt_loc + 1] if self.create_first_day_stats: if len(self.algorithm_cumulative_leverages) == 1: self.algorithm_cumulative_leverages = np.append( 0.0, self.algorithm_cumulative_leverages) if not len(self.algorithm_returns) and len(self.benchmark_returns): message = "Mismatch between benchmark_returns ({bm_count}) and \ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}" message = message.format(bm_count=len(self.benchmark_returns), algo_count=len(self.algorithm_returns), start=self.start_session, end=self.end_session, dt=dt) raise Exception(message) self.update_current_max() self.benchmark_volatility[dt_loc] = annual_volatility( benchmark_returns_series) self.algorithm_volatility[dt_loc] = annual_volatility( algorithm_returns_series) # caching the treasury rates for the minutely case is a # big speedup, because it avoids searching the treasury # curves on every minute. # In both minutely and daily, the daily curve is always used. treasury_end = dt.replace(hour=0, minute=0) if np.isnan(self.daily_treasury[treasury_end]): treasury_period_return = choose_treasury( self.treasury_curves, self.start_session, treasury_end, self.trading_calendar, ) self.daily_treasury[treasury_end] = treasury_period_return self.treasury_period_return = self.daily_treasury[treasury_end] self.excess_returns[dt_loc] = ( self.algorithm_cumulative_returns[dt_loc] - self.treasury_period_return) self.beta[dt_loc] = beta(algorithm_returns_series, benchmark_returns_series) self.alpha[dt_loc] = alpha(algorithm_returns_series, benchmark_returns_series) self.sharpe[dt_loc] = sharpe_ratio(algorithm_returns_series, benchmark_returns_series) self.downside_risk[dt_loc] = downside_risk(algorithm_returns_series, benchmark_returns_series) self.sortino[dt_loc] = sortino_ratio(algorithm_returns_series, benchmark_returns_series) self.information[dt_loc] = information_ratio(algorithm_returns_series, benchmark_returns_series) self.max_drawdown = max_drawdown(algorithm_returns_series) self.max_drawdowns[dt_loc] = self.max_drawdown self.max_leverage = self.calculate_max_leverage() self.max_leverages[dt_loc] = self.max_leverage
def run_turtle(): PROPERTY = START_MONEY CASH = START_MONEY show_df = None show_df = stock_df_dict['NDX'].copy() order_df = None order_df = pd.DataFrame(columns=[ 'buy_date', 'symbol', 'buy_count', 'buy_price', 'buy_reason', 'sell_date', 'sell_price', 'sell_reason', 'profit', 'cash', 'property' ]) count_day = 0 yesterday = None for today in pd.period_range(start=start_date, end=end_date, freq='D'): count_day += 1 if yesterday is None: yesterday = today continue if today not in stock_df_dict['NDX'].index: continue if IS_HAPPY_MONEY: if PROPERTY > START_MONEY * 2: global HAPPY_MONEY HAPPY_MONEY += int(START_MONEY / 2) PROPERTY -= int(START_MONEY / 2) CASH = PROPERTY # 买卖过程 sell_signal = [] buy_signal = [] for symbol in NASDAQ100[:]: # for symbol in ['TSLA']: if symbol in [ 'ALGN', 'ROST', 'ORLY', 'ESRX', 'ULTA', 'REGN', 'MNST' ]: # continue pass if symbol == 'NDX': continue if today not in stock_df_dict[ symbol].index or yesterday not in stock_df_dict[ symbol].index: continue # 突破下行趋势,清仓退出 order_arr = order_df.to_records(index=False) if len(order_arr[(order_arr.symbol == symbol) & (order_arr.sell_price == 0)]) != 0: is_sell = False for idx in order_df[(order_df['symbol'] == symbol) & (order_df['sell_price'] == 0)].index: if order_df.loc[idx, 'buy_reason'] == 'SHORT': is_sell = ( stock_df_dict[symbol].loc[today, 'open'] <= stock_df_dict[symbol].loc[today, 'ROLLING_%d_MIN' % TURTLE_SHORT_SELL_N]) if order_df.loc[idx, 'buy_reason'] == 'LONG': is_sell = ( stock_df_dict[symbol].loc[today, 'open'] <= stock_df_dict[symbol].loc[today, 'ROLLING_%d_MIN' % TURTLE_LONG_SELL_N]) if is_sell: CASH += order_df.loc[idx, 'buy_count'] * \ stock_df_dict[symbol].loc[today, 'open'] order_df.loc[idx, 'sell_date'] = today order_df.loc[idx, 'sell_price'] = stock_df_dict[symbol].loc[ today, 'open'] order_df.loc[idx, 'sell_reason'] = 'EXIT' order_df.loc[idx, 'profit'] = \ (order_df.loc[idx, 'sell_price'] - order_df.loc[idx, 'buy_price']) * order_df.loc[idx, 'buy_count'] # print(today, '退出', stock_df_dict[symbol].loc[today, 'open'], CASH) # 突破上行趋势,买入一份 order_arr = order_df.to_records(index=False) if stock_df_dict[symbol].loc[ today, 'MA30'] >= stock_df_dict[symbol].loc[today, 'MA180']: is_buy = False if stock_df_dict[symbol].loc[today, 'open'] >= stock_df_dict[ symbol].loc[today, 'ROLLING_%d_MAX' % TURTLE_LONG_BUY_N]: is_buy = True buy_reason = 'LONG' elif stock_df_dict[symbol].loc[today, 'open'] >= stock_df_dict[ symbol].loc[today, 'ROLLING_%d_MAX' % TURTLE_SHORT_BUY_N]: is_buy = True buy_reason = 'SHORT' if is_buy: buy_count = 0 if CASH >= PROPERTY / TURTLE_POS: buy_count = int( (PROPERTY / TURTLE_POS) / stock_df_dict[symbol].loc[today, 'open']) if buy_count > 0: CASH -= buy_count * \ stock_df_dict[symbol].loc[today, 'open'] # print(today, '买入', buy_count, stock_df_dict[symbol].loc[today, 'open'], CASH) order_df = order_df.append( { 'buy_date': today, 'symbol': symbol, 'buy_count': buy_count, 'buy_price': stock_df_dict[symbol].loc[today, 'open'], 'buy_reason': buy_reason, 'sell_date': pd.np.nan, 'sell_price': 0, 'profit': 0, 'cash': CASH, 'property': PROPERTY, }, ignore_index=True) # 每天盘点财产 show_df.loc[today, 'CASH_TURTLE_%d_%d_%d' % (TURTLE_POS, TURTLE_LONG_BUY_N, TURTLE_LONG_SELL_N)] = CASH PROPERTY = CASH + \ sum( [ stock_df_dict[order_df.loc[idx, 'symbol']].loc[today, 'open'] * order_df.loc[idx, 'buy_count'] for idx in order_df.loc[order_df['sell_price'] == 0].index ] ) show_df.loc[today, 'PROPERTY_TURTLE_%d_%d_%d' % (TURTLE_POS, TURTLE_LONG_BUY_N, TURTLE_LONG_SELL_N)] = PROPERTY yesterday = today # 最终结果 print('CASH', CASH) print('HAPPY_MONEY', HAPPY_MONEY) print('PROPERTY', PROPERTY) benchmark_symbol = 'NDX' s_p = stock_df_dict[benchmark_symbol][start_date:].iloc[0].open e_p = stock_df_dict[benchmark_symbol].iloc[-1].open print(benchmark_symbol, s_p, e_p, e_p / s_p) show_df = show_df[start_date:].dropna(how='any', inplace=False) show_df['strategy_pct'] = show_df['PROPERTY_TURTLE_%d_%d_%d' % (TURTLE_POS, TURTLE_LONG_BUY_N, TURTLE_LONG_SELL_N)].pct_change() # show_df['benchmark_pct'] = show_df['open'].pct_change() show_df['benchmark_pct'] = stock_df_dict[benchmark_symbol].open.pct_change( ) # print('cum_returns', emp.cum_returns(show_df.strategy_pct)) print('max_drawdown', emp.max_drawdown(show_df.strategy_pct)) print( 'MDD', MDD(show_df['PROPERTY_TURTLE_%d_%d_%d' % (TURTLE_POS, TURTLE_LONG_BUY_N, TURTLE_LONG_SELL_N)])) print('annual_return', emp.annual_return(show_df.strategy_pct)) print('annual_volatility', emp.annual_volatility(show_df.strategy_pct, period='daily')) print('calmar_ratio', emp.calmar_ratio(show_df.strategy_pct)) print('sharpe_ratio', emp.sharpe_ratio(returns=show_df.strategy_pct)) print( 'alpha', emp.alpha(returns=show_df.strategy_pct, factor_returns=show_df.benchmark_pct, risk_free=0.00)) print( 'beta', emp.beta(returns=show_df.strategy_pct, factor_returns=show_df.benchmark_pct, risk_free=0.00))
def vizResults(slippageAdjustedReturn, returnStream, factorReturn, plotting = False): ##ENSURE EQUAL LENGTH factorReturn = factorReturn[returnStream.index[0]:] ##IF FACTOR DOES NOT START AT SAME SPOT CAN CREATE VERY SKEWED RESULTS ##CALCULATE SHARPE WITH SLIPPAGE sharpeDiffSlippage = empyrical.sharpe_ratio(slippageAdjustedReturn) - empyrical.sharpe_ratio(factorReturn) relativeSharpeSlippage = sharpeDiffSlippage / empyrical.sharpe_ratio(factorReturn) * (empyrical.sharpe_ratio(factorReturn)/abs(empyrical.sharpe_ratio(factorReturn))) alpha, beta = empyrical.alpha_beta(returnStream, factorReturn) alphaSlippage, betaSlippage = empyrical.alpha_beta(slippageAdjustedReturn, factorReturn) metrics = {"SHARPE": empyrical.sharpe_ratio(returnStream), "SHARPE SLIPPAGE":empyrical.sharpe_ratio(slippageAdjustedReturn), "STABILITY": empyrical.stability_of_timeseries(returnStream), "ALPHA":alpha, "ALPHA SLIPPAGE":alphaSlippage, "BETA":abs(beta), "ANNUALIZED RETURN": empyrical.annual_return(returnStream)[0], "ACTIVITY": np.count_nonzero(returnStream)/float(len(returnStream)), "TREYNOR": ((empyrical.annual_return(returnStream.values)[0] - empyrical.annual_return(factorReturn.values)[0]) \ / abs(empyrical.beta(returnStream, factorReturn))), "RAW BETA":abs(empyrical.alpha_beta(returnStream.apply(lambda x:applyBinary(x), axis=0), factorReturn.apply(lambda x:applyBinary(x), axis=0))[1]), "SHARPE DIFFERENCE": empyrical.sharpe_ratio(returnStream) - empyrical.sharpe_ratio(factorReturn), "RELATIVE SHARPE": (empyrical.sharpe_ratio(returnStream) - empyrical.sharpe_ratio(factorReturn))/empyrical.sharpe_ratio(factorReturn) * (empyrical.sharpe_ratio(factorReturn)/abs(empyrical.sharpe_ratio(factorReturn))), "FACTOR SHARPE": empyrical.sharpe_ratio(factorReturn), "SHARPE DIFFERENCE SLIPPAGE":sharpeDiffSlippage, "RELATIVE SHARPE SLIPPAGE":relativeSharpeSlippage, } metrics["FACTOR PROFITABILITY"] = len((factorReturn.values)[factorReturn.values > 0])/len(factorReturn.values) metrics["PROFITABILITY"] = len((returnStream.values)[returnStream.values > 0])/len(returnStream.values) metrics["PROFITABILITY DIFFERENCE"] = metrics["PROFITABILITY"] - metrics["FACTOR PROFITABILITY"] metrics["PROFITABILITY SLIPPAGE"] = len((slippageAdjustedReturn.values)[slippageAdjustedReturn.values > 0])/len(slippageAdjustedReturn.values) metrics["ACTIVE PROFITABILITY"] = len((returnStream.values)[returnStream.values > 0])/len((returnStream.values)[returnStream.values != 0]) metrics["ACTIVE PROFITABILITY SLIPPAGE"] = len((slippageAdjustedReturn.values)[slippageAdjustedReturn.values > 0])/len((slippageAdjustedReturn.values)[slippageAdjustedReturn.values != 0]) metrics["TOTAL DAYS SEEN"] = len(returnStream) metrics["SHARPE SLIPPAGE DECAY"] = metrics["SHARPE DIFFERENCE SLIPPAGE"] - metrics["SHARPE DIFFERENCE"] ##MEASURES BINARY STABILITY OF PREDICTIONS metrics["EXTREME STABILITY ROLLING 600"] = (returnStream.rolling(600, min_periods=600).apply(lambda x:empyrical.stability_of_timeseries(applyBinarySkipZero(x)) * (-1 if x[-1] - x[0] < 0 else 1)).dropna()).min().values[0] metrics["EXTREME STABILITY"] = empyrical.stability_of_timeseries(applyBinarySkipZero(returnStream.values)) rollingPeriod = 252 rollingSharpe = returnStream.rolling(rollingPeriod, min_periods=rollingPeriod).apply(lambda x:empyrical.sharpe_ratio(x)).dropna() rollingSharpe.columns = ["252 Day Rolling Sharpe"] rollingSharpeFactor = factorReturn.rolling(rollingPeriod, min_periods=rollingPeriod).apply(lambda x:empyrical.sharpe_ratio(x)).dropna() rollingSharpe = rollingSharpe.join(rollingSharpeFactor) rollingSharpe.columns = ["252 Day Rolling Sharpe Algo", "252 Day Rolling Sharpe Factor"] if len(rollingSharpe["252 Day Rolling Sharpe Algo"].values) > 50: diffSharpe = pd.DataFrame(rollingSharpe.apply(lambda x: x[0] - x[1], axis=1), columns=["Sharpe Difference"]) metrics["SHARPE DIFFERENCE MIN"] = np.percentile(diffSharpe["Sharpe Difference"].values, 1) metrics["SHARPE DIFFERENCE AVERAGE"] = np.percentile(diffSharpe["Sharpe Difference"].values, 50) difVals = diffSharpe["Sharpe Difference"].values metrics["SHARPE DIFFERENCE GREATER THAN 0"] = len(difVals[np.where(difVals > 0)])/float(len(difVals)) metrics["25TH PERCENTILE SHARPE DIFFERENCE"] = np.percentile(diffSharpe["Sharpe Difference"].values, 25) ### relDiffSharpe = pd.DataFrame(rollingSharpe.apply(lambda x: (x[0] - x[1])/x[1] * (x[1]/abs(x[1])), axis=1), columns=["Sharpe Difference"]) metrics["RELATIVE SHARPE DIFFERENCE MIN"] = np.percentile(relDiffSharpe["Sharpe Difference"].values, 1) metrics["RELATIVE SHARPE DIFFERENCE AVERAGE"] = np.percentile(relDiffSharpe["Sharpe Difference"].values, 50) relDifVals = relDiffSharpe["Sharpe Difference"].values metrics["RELATIVE SHARPE DIFFERENCE GREATER THAN 0"] = len(relDifVals[np.where(relDifVals > 0)])/float(len(relDifVals)) metrics["25TH PERCENTILE RELATIVE SHARPE DIFFERENCE"] = np.percentile(relDiffSharpe["Sharpe Difference"].values, 25) ### metrics["ROLLING SHARPE BETA"] = abs(empyrical.beta(rollingSharpe["252 Day Rolling Sharpe Algo"], rollingSharpe["252 Day Rolling Sharpe Factor"])) metrics["25TH PERCENTILE SHARPE"] = np.percentile(rollingSharpe["252 Day Rolling Sharpe Algo"].values, 25) metrics["MIN ROLLING SHARPE"] = np.percentile(rollingSharpe["252 Day Rolling Sharpe Algo"].values, 1) rollingDownside = returnStream.rolling(rollingPeriod, min_periods=rollingPeriod).apply(lambda x:empyrical.max_drawdown(x)).dropna() rollingDownside.columns = ["252 Day Rolling Downside"] rollingDownsideFactor = factorReturn.rolling(rollingPeriod, min_periods=rollingPeriod).apply(lambda x:empyrical.max_drawdown(x)).dropna() rollingDownside = rollingDownside.join(rollingDownsideFactor) rollingDownside.columns = ["252 Day Rolling Downside Algo", "252 Day Rolling Downside Factor"] metrics["ROLLING SHARPE STABILITY"] = abs(stats.linregress(np.arange(len(rollingSharpe["252 Day Rolling Sharpe Algo"].values)), rollingSharpe["252 Day Rolling Sharpe Algo"].values).rvalue) rollingReturn = returnStream.rolling(rollingPeriod, min_periods=rollingPeriod).apply(lambda x:empyrical.cum_returns(x)[-1]).dropna() rollingReturn.columns = ["ROLLING RETURN"] metrics["SMART INFORMATION RATIO"] = (np.percentile(rollingReturn["ROLLING RETURN"].values, 25) - empyrical.annual_return(factorReturn.values[0]))\ / returnStream.values.std() metrics["ROLLING SHARPE ERROR"] = rollingSharpe["252 Day Rolling Sharpe Algo"].std() metrics["ONE STD SHARPE"] = empyrical.sharpe_ratio(slippageAdjustedReturn) - metrics["ROLLING SHARPE ERROR"] if plotting == True: import matplotlib.pyplot as plt rollingSharpe.plot() rollingDownside.plot() rollingPeriod = 90 rollingSharpe = returnStream.rolling(rollingPeriod, min_periods=rollingPeriod).apply(lambda x:empyrical.sharpe_ratio(x)).dropna() rollingSharpe.columns = ["90 Day Rolling Sharpe"] if len(rollingSharpe["90 Day Rolling Sharpe"].values) > 50: metrics["25TH PERCENTILE SHARPE 90"] = np.percentile(rollingSharpe["90 Day Rolling Sharpe"].values, 25) metrics["MIN ROLLING SHARPE 90"] = np.percentile(rollingSharpe["90 Day Rolling Sharpe"].values, 1) metrics["ROLLING SHARPE ERROR 90"] = rollingSharpe["90 Day Rolling Sharpe"].std() metrics["SHARPE TO MIN RATIO 90"] = metrics["SHARPE"] / abs(metrics["MIN ROLLING SHARPE 90"]) metrics["MIN PROFITABILITY 90"] = np.percentile(returnStream.rolling(rollingPeriod, min_periods=rollingPeriod).apply(lambda x:len((x)[x > 0])/len(x)).dropna().values, 1) metrics["PROFITABILITY DROP 90"] = metrics["PROFITABILITY"] - metrics["MIN PROFITABILITY 90"] metrics["25TH PROFITABILITY 90"] = np.percentile(returnStream.rolling(rollingPeriod, min_periods=rollingPeriod).apply(lambda x:len((x)[x > 0])/len(x)).dropna().values, 25) metrics["MIN FACTOR PROFITABILITY 90"] = np.percentile(factorReturn.rolling(rollingPeriod, min_periods=rollingPeriod).apply(lambda x:len((x)[x > 0])/len(x)).dropna().values, 1) metrics["MIN PROFITABILITY DIFFERENCE 90"] = metrics["MIN PROFITABILITY 90"] - metrics["MIN FACTOR PROFITABILITY 90"] rollingPeriod = 45 rollingSharpe = returnStream.rolling(rollingPeriod, min_periods=rollingPeriod).apply(lambda x:empyrical.sharpe_ratio(x)).dropna() rollingSharpe.columns = ["45 Day Rolling Sharpe"] if len(rollingSharpe["45 Day Rolling Sharpe"].values) > 50: metrics["25TH PERCENTILE SHARPE 45"] = np.percentile(rollingSharpe["45 Day Rolling Sharpe"].values, 25) metrics["MIN ROLLING SHARPE 45"] = np.percentile(rollingSharpe["45 Day Rolling Sharpe"].values, 1) metrics["ROLLING SHARPE ERROR 45"] = rollingSharpe["45 Day Rolling Sharpe"].std() metrics["SHARPE TO MIN RATIO 45"] = metrics["SHARPE"] / abs(metrics["MIN ROLLING SHARPE 45"]) metrics["MIN PROFITABILITY 45"] = np.percentile(returnStream.rolling(rollingPeriod, min_periods=rollingPeriod).apply(lambda x:len((x)[x > 0])/len(x)).dropna().values, 1) metrics["PROFITABILITY DROP 45"] = metrics["PROFITABILITY"] - metrics["MIN PROFITABILITY 45"] metrics["25TH PROFITABILITY 45"] = np.percentile(returnStream.rolling(rollingPeriod, min_periods=rollingPeriod).apply(lambda x:len((x)[x > 0])/len(x)).dropna().values, 25) metrics["MIN FACTOR PROFITABILITY 45"] = np.percentile(factorReturn.rolling(rollingPeriod, min_periods=rollingPeriod).apply(lambda x:len((x)[x > 0])/len(x)).dropna().values, 1) metrics["MIN PROFITABILITY DIFFERENCE 45"] = metrics["MIN PROFITABILITY 45"] - metrics["MIN FACTOR PROFITABILITY 45"] returns = returnStream.apply(lambda x:empyrical.cum_returns(x)) returns.columns = ["algo"] factorReturn = factorReturn.apply(lambda x:empyrical.cum_returns(x)) returns = returns.join(factorReturn) returns.columns = ["Algo Return", "Factor Return"] ##FORCE SHOW if plotting == True: import matplotlib.pyplot as plt returns.plot() plt.show() return metrics
# 画出收益曲线图 draw_return_rate_line(perf) return_list = perf['returns'] # 计算年化收益率 ann_return = annual_return(return_list) # 计算累计收益率 cum_return_list = cum_returns(return_list) # 计算sharp ratio sharp = sharpe_ratio(return_list) # 最大回撤 max_drawdown_ratio = max_drawdown(return_list) print("年化收益率 = {:.2%}, 累计收益率 = {:.2%}, 最大回撤 = {:.2%}, 夏普比率 = {:.2f} ".format (ann_return, cum_return_list[-1], max_drawdown_ratio, sharp)) returns = pd.Series( index=pd.date_range('2017-03-10', '2017-03-19'), data=(-0.012143, 0.045350, 0.030957, 0.004902, 0.002341, -0.02103, 0.00148, 0.004820, -0.00023, 0.01201) ) benchmark_returns = pd.Series( index=pd.date_range('2017-03-10', '2017-03-19'), data=(-0.031940, 0.025350, -0.020957, -0.000902, 0.007341, -0.01103, 0.00248, 0.008820, -0.00123, 0.01091) ) alpha_return = alpha(returns=returns, factor_returns=benchmark_returns, risk_free=0.01) beta_return = beta(returns=returns, factor_returns=benchmark_returns, risk_free=0.01) print("alpha_return", alpha_return) print("\nbeta_return", beta_return)
# ACTUAL ASSET'S BACKTEST bt = get_backtest("hashcode") bt_returns = bt.daily_performance['returns'] bt_positions = bt.pyfolio_positions bt_transactions = bt.pyfolio_transactions # BENCHMARK BACKTEST TOO benchmark = get_backtest("another hashcode") bn_returns = benchmark.daily_performance['returns'] bn_positions = benchmark.pyfolio_positions bn_transactions = benchmark.pyfolio_transactions # CALC'ING THE SHARPE RATIO bt_sr = empyrical.sharpe_ratio(bt_returns) bn_sr = empyrical.sharpe_ratio(bn_returns) # CALC'ING BETA beta = empyrical.beta(bt_returns, bn_returns) # PLOTTING WITH PyFolio (ALL TYPES OF PLOTS AVAILABLE - pf.plotting.type_of_plot()) # CUMULATIVE RETURNS plt.subplot(2, 1, 1) pf.plotting.plot_rolling_returns(bt_returns, bn_returns) # DAILY (NON-CUMULATIVE) RETURNS plt.subplot(2, 1, 2) pf.plotting.plot_returns(bt_returns) plt.tight_layout() # # ACTUAL ALGORITHM TO BE BACKTESTED (WHILE THE CODE ABOVE DOES ANALYSIS, USING THE BACKTESTING RESULTS) # COMES WITH initialize() & shit #
def beta(portfolio_daily_returns, benchmark_returns=None): if benchmark_returns is None: benchmark_returns = sdata.get_sp500_index_returns( portfolio_daily_returns.index[0], portfolio_daily_returns.index[-1]) return ep.beta(portfolio_daily_returns, benchmark_returns)
def calculate_metrics(self): self.benchmark_period_returns = \ cum_returns(self.benchmark_returns).iloc[-1] self.algorithm_period_returns = \ cum_returns(self.algorithm_returns).iloc[-1] if not self.algorithm_returns.index.equals( self.benchmark_returns.index ): message = "Mismatch between benchmark_returns ({bm_count}) and \ algorithm_returns ({algo_count}) in range {start} : {end}" message = message.format( bm_count=len(self.benchmark_returns), algo_count=len(self.algorithm_returns), start=self._start_session, end=self._end_session ) raise Exception(message) self.num_trading_days = len(self.benchmark_returns) self.trading_day_counts = pd.stats.moments.rolling_count( self.algorithm_returns, self.num_trading_days) self.mean_algorithm_returns = \ self.algorithm_returns.cumsum() / self.trading_day_counts self.benchmark_volatility = annual_volatility(self.benchmark_returns) self.algorithm_volatility = annual_volatility(self.algorithm_returns) self.treasury_period_return = choose_treasury( self.treasury_curves, self._start_session, self._end_session, self.trading_calendar, ) self.sharpe = sharpe_ratio( self.algorithm_returns, ) # The consumer currently expects a 0.0 value for sharpe in period, # this differs from cumulative which was np.nan. # When factoring out the sharpe_ratio, the different return types # were collapsed into `np.nan`. # TODO: Either fix consumer to accept `np.nan` or make the # `sharpe_ratio` return type configurable. # In the meantime, convert nan values to 0.0 if pd.isnull(self.sharpe): self.sharpe = 0.0 self.downside_risk = downside_risk( self.algorithm_returns ) self.sortino = sortino_ratio( self.algorithm_returns, _downside_risk=self.downside_risk ) self.information = information_ratio( self.algorithm_returns, self.benchmark_returns ) self.beta = beta( self.algorithm_returns, self.benchmark_returns ) self.alpha = alpha( self.algorithm_returns, self.benchmark_returns, _beta=self.beta ) self.excess_return = self.algorithm_period_returns - \ self.treasury_period_return self.max_drawdown = max_drawdown(self.algorithm_returns) self.max_leverage = self.calculate_max_leverage()
def work(PARAMS): info('work %s' % str(PARAMS)) stock_df_dict = None show_df = None order_df = None PROPERTY = None STRATEGY = PARAMS[0] POS = PARAMS[1] N = PARAMS[2] K = PARAMS[3] M = PARAMS[4] global ROTATION_LIST ROTATION_LIST = ROTATION_LIST stock_df_dict = get_stock_df_dict(N, M) show_df, order_df, PROPERTY = run_turtle(ROTATION_LIST, stock_df_dict, STRATEGY, POS, N, K, M) df = show_df.dropna(how='any', inplace=False).copy() df = df.loc[start_date:end_date] algo = df['PROPERTY'].pct_change() benchmark = df.open.pct_change() DAYS_ALL = len(df) DAYS_NOFULLHOLD = len(df[df['CASH'] > (df['PROPERTY'] / POS)]) output_str = '' for y in range(int(start_date.split('-')[0]), int(end_date.split('-')[0]) + 1, 1): # info('y = %d' % y) y_df = df.loc['%d-01-01' % y:'%d-01-01' % (y + 1)] if len(y_df) == 0: continue y_algo = y_df['PROPERTY'].pct_change() # info(y_algo) y_benchmark = y_df.open.pct_change() # info('y_benc') result = '%d-%d,%.3f,%.3f,%.3f,%.3f' % ( y, y + 1, emp.cum_returns(y_algo)[-1], emp.cum_returns(y_benchmark)[-1], emp.max_drawdown(y_algo), emp.max_drawdown(y_benchmark) ) output_str += result output_str += ';' # info(output_str) df = order_df.copy() df['pro_pct'] = (df.borrow_price - df.return_price) / df.return_price df = df.loc[:, ['symbol', 'pro_pct']] df = df.groupby(by='symbol').sum() buy_stock_count = len(df) score_sr = pd.Series({ 'START': start_date, 'END': end_date, 'STRATEGY': STRATEGY, 'POS': POS, 'N': N, 'K': K, 'M': M, 'ORDER': len(order_df), 'STOCK': buy_stock_count, 'RETURN_ALGO': emp.cum_returns(algo)[-1], 'RETURN_BENC': emp.cum_returns(benchmark)[-1], 'MAXDROPDOWN_ALGO': emp.max_drawdown(algo), 'MAXDROPDOWN_BENC': emp.max_drawdown(benchmark), 'WINRATE_ORDER': len(order_df[order_df.profit > 0]) / len(order_df[order_df.profit != 0]), 'WINRATE_YEARLY': 0, 'ANNUAL_RETURN': emp.annual_return(algo), 'ANNUAL_VOLATILITY': emp.annual_volatility(algo, period='daily'), 'CALMAR_RATIO': emp.calmar_ratio(algo), 'SHARPE_RATIO': emp.sharpe_ratio(returns=algo), 'ALPHA': emp.alpha(returns=algo, factor_returns=benchmark, risk_free=0.00), 'BETA': emp.beta(returns=algo, factor_returns=benchmark, risk_free=0.00), 'DAYS_ALL': DAYS_ALL, 'DAYS_NOFULLHOLD': DAYS_NOFULLHOLD, 'RET_PER_YEAR': output_str, }) YEAR_COUNT = 0 ALGO_WIN_YEAR_COUNT = 0 df = show_df.dropna(how='any', inplace=False).copy() df = df.loc[start_date:end_date] for y in range(int(start_date.split('-')[0]), int(end_date.split('-')[0]) + 1, 1): y_df = df.loc['%d-01-01' % y:'%d-01-01' % (y + 1)] # info('y = %d' % y) if len(y_df) == 0: continue y_algo = y_df['PROPERTY'].pct_change() y_benchmark = y_df.open.pct_change() score_sr['RETURN_ALGO_%d' % y] = emp.cum_returns(y_algo)[-1] score_sr['RETURN_BENC_%d' % y] = emp.cum_returns(y_benchmark)[-1] YEAR_COUNT += 1 if score_sr['RETURN_ALGO_%d' % y] > score_sr['RETURN_BENC_%d' % y]: ALGO_WIN_YEAR_COUNT += 1 score_sr['WINRATE_YEARLY'] = ALGO_WIN_YEAR_COUNT / YEAR_COUNT return PARAMS, score_sr, order_df
def update(self, dt, algorithm_returns, benchmark_returns, leverage): # Keep track of latest dt for use in to_dict and other methods # that report current state. self.latest_dt = dt dt_loc = self.cont_index.get_loc(dt) self.latest_dt_loc = dt_loc self.algorithm_returns_cont[dt_loc] = algorithm_returns self.algorithm_returns = self.algorithm_returns_cont[: dt_loc + 1] algorithm_returns_series = pd.Series(self.algorithm_returns) self.num_trading_days = len(self.algorithm_returns) if self.create_first_day_stats: if len(self.algorithm_returns) == 1: self.algorithm_returns = np.append(0.0, self.algorithm_returns) self.algorithm_cumulative_returns[dt_loc] = cum_returns(algorithm_returns_series).iloc[-1] algo_cumulative_returns_to_date = self.algorithm_cumulative_returns[: dt_loc + 1] self.mean_returns_cont[dt_loc] = algo_cumulative_returns_to_date[dt_loc] / self.num_trading_days self.mean_returns = self.mean_returns_cont[: dt_loc + 1] self.annualized_mean_returns_cont[dt_loc] = self.mean_returns_cont[dt_loc] * 252 self.annualized_mean_returns = self.annualized_mean_returns_cont[: dt_loc + 1] if self.create_first_day_stats: if len(self.mean_returns) == 1: self.mean_returns = np.append(0.0, self.mean_returns) self.annualized_mean_returns = np.append(0.0, self.annualized_mean_returns) self.benchmark_returns_cont[dt_loc] = benchmark_returns self.benchmark_returns = self.benchmark_returns_cont[: dt_loc + 1] benchmark_returns_series = pd.Series(self.benchmark_returns) if self.create_first_day_stats: if len(self.benchmark_returns) == 1: self.benchmark_returns = np.append(0.0, self.benchmark_returns) self.benchmark_cumulative_returns[dt_loc] = cum_returns(benchmark_returns_series).iloc[-1] benchmark_cumulative_returns_to_date = self.benchmark_cumulative_returns[: dt_loc + 1] self.mean_benchmark_returns_cont[dt_loc] = benchmark_cumulative_returns_to_date[dt_loc] / self.num_trading_days self.mean_benchmark_returns = self.mean_benchmark_returns_cont[:dt_loc] self.annualized_mean_benchmark_returns_cont[dt_loc] = self.mean_benchmark_returns_cont[dt_loc] * 252 self.annualized_mean_benchmark_returns = self.annualized_mean_benchmark_returns_cont[: dt_loc + 1] self.algorithm_cumulative_leverages_cont[dt_loc] = leverage self.algorithm_cumulative_leverages = self.algorithm_cumulative_leverages_cont[: dt_loc + 1] if self.create_first_day_stats: if len(self.algorithm_cumulative_leverages) == 1: self.algorithm_cumulative_leverages = np.append(0.0, self.algorithm_cumulative_leverages) if not len(self.algorithm_returns) and len(self.benchmark_returns): message = "Mismatch between benchmark_returns ({bm_count}) and \ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}" message = message.format( bm_count=len(self.benchmark_returns), algo_count=len(self.algorithm_returns), start=self.start_session, end=self.end_session, dt=dt, ) raise Exception(message) self.update_current_max() self.benchmark_volatility[dt_loc] = annual_volatility(benchmark_returns_series) self.algorithm_volatility[dt_loc] = annual_volatility(algorithm_returns_series) # caching the treasury rates for the minutely case is a # big speedup, because it avoids searching the treasury # curves on every minute. # In both minutely and daily, the daily curve is always used. treasury_end = dt.replace(hour=0, minute=0) if np.isnan(self.daily_treasury[treasury_end]): treasury_period_return = choose_treasury( self.treasury_curves, self.start_session, treasury_end, self.trading_calendar ) self.daily_treasury[treasury_end] = treasury_period_return self.treasury_period_return = self.daily_treasury[treasury_end] self.excess_returns[dt_loc] = self.algorithm_cumulative_returns[dt_loc] - self.treasury_period_return self.beta[dt_loc] = beta(algorithm_returns_series, benchmark_returns_series) self.alpha[dt_loc] = alpha(algorithm_returns_series, benchmark_returns_series, _beta=self.beta[dt_loc]) self.sharpe[dt_loc] = sharpe_ratio(algorithm_returns_series) self.downside_risk[dt_loc] = downside_risk(algorithm_returns_series) self.sortino[dt_loc] = sortino_ratio(algorithm_returns_series, _downside_risk=self.downside_risk[dt_loc]) self.information[dt_loc] = information_ratio(algorithm_returns_series, benchmark_returns_series) self.max_drawdown = max_drawdown(algorithm_returns_series) self.max_drawdowns[dt_loc] = self.max_drawdown self.max_leverage = self.calculate_max_leverage() self.max_leverages[dt_loc] = self.max_leverage
def treynor_ratio_annualised(excess_returns, index_excess_returns): _beta = ep.beta(excess_returns, index_excess_returns) return 252 * excess_returns.mean() / _beta
# Get benchmark returns benchmark_rets = get_backtest('5986c511c94d014fc81acf7b') bm_returns = benchmark_rets.daily_performance['returns'] bm_positions = benchmark_rets.pyfolio_positions bm_transactions = benchmark_rets.pyfolio_transactions # ### Use Algo from Leverage Lecture # Use same algo as in the Leverage Lecture! bt = get_backtest('5986b969dbab994fa4264696') bt_returns = bt.daily_performance['returns'] bt_positions = bt.pyfolio_positions bt_transactions = bt.pyfolio_transactions bt_returns.plot() # In[46]: empyrical.beta(bt_returns, bm_returns) # # PyFolio Plots benchmark_rets = bm_returns # Cumulative Returns plt.subplot(2, 1, 1) pf.plotting.plot_rolling_returns(bt_returns, benchmark_rets) # Daily, Non-Cumulative Returns plt.subplot(2, 1, 2) pf.plotting.plot_returns(bt_returns) plt.tight_layout() # In[49]: fig = plt.figure(1) plt.subplot(1, 3, 1) pf.plot_annual_returns(bt_returns) plt.subplot(1, 3, 2) pf.plot_monthly_returns_dist(bt_returns)
def getBeta(self): return empyrical.beta(self.returns, self.riskFreeRate)
# Process the Benchmark Results to Normalize the data and have a comparison benchmark = get_backtest('insert benchmark hash') benchmark_returns = benchmark.daily_performance['returns'] benchmark_positions = benchmark.pyfolio_positions benchmark_transactions = benchmark.pyfolio_transactions ################################## # Sharpe Ratio # # for backtest and benchmark # # Combined Beta # ################################## backtest_sharpe = empyrical.sharpe_ratio(backtest_returns) benchmark_sharpe = empyrical.sharpe_ratio(benchmark_returns) combined_beta = empyrical.beta(backtest_returns, benchmark_returns) ############################ # Round Trip # # Tear Sheet # # right = positive # # left = negative # ############################ tear_sheet = pf.create_round_trip_tear_sheet(backtest_returns, backtest_positions, backtest_transactions) # Cumulative Returns plt.subplot(2, 1, 1) pf.plotting.plot_rolling_returns(backtest_returns, benchmark_returns)
def runModelsChunksSkipMP(self, dataOfInterest, daysToCheck = None): xVals, yVals, yIndex, xToday = self.walkForward.generateWindows(dataOfInterest) mpEngine = mp.get_context('fork') with mpEngine.Manager() as manager: returnDict = manager.dict() identifiersToCheck = [] for i in range(len(xVals) - 44): ##44 is lag...should not overlap with any other predictions or will ruin validity of walkforward optimization if i < 600: ##MIN TRAINING continue identifiersToCheck.append(str(i)) if daysToCheck is not None: identifiersToCheck = identifiersToCheck[-daysToCheck:] ##FIRST CHECK FIRST 500 IDENTIFIERS AND THEN IF GOOD CONTINUE identifierWindows = [identifiersToCheck[:252], identifiersToCheck[252:600], identifiersToCheck[600:900], identifiersToCheck[900:1200], identifiersToCheck[1200:]] ##EXACTLY TWO YEARS returnStream = None factorReturn = None predictions = None slippageAdjustedReturn = None shortSeen = 0 for clippedIdentifiers in identifierWindows: splitIdentifiers = np.array_split(np.array(clippedIdentifiers), 16) runningP = [] k = 0 for identifiers in splitIdentifiers: p = mpEngine.Process(target=endToEnd.runDayChunking, args=(self, xVals, yVals, identifiers, returnDict,k)) p.start() runningP.append(p) k += 1 while len(runningP) > 0: newP = [] for p in runningP: if p.is_alive() == True: newP.append(p) else: p.join() runningP = newP preds = [] actuals = [] days = [] for i in clippedIdentifiers: preds.append(returnDict[i]) actuals.append(yVals[int(i) + 44]) days.append(yIndex[int(i) + 44]) loss = log_loss(np.array(endToEnd.transformTargetArr(np.array(actuals), self.threshold)), np.array(preds)) roc_auc = roc_auc_score(np.array(endToEnd.transformTargetArr(np.array(actuals), self.threshold)), np.array(preds)) accuracy = accuracy_score(np.array(endToEnd.transformTargetArr(np.array(actuals), self.threshold)), np.array(preds).round()) print(loss, roc_auc, accuracy) ##CREATE ACCURATE BLENDING ACROSS DAYS predsTable = pd.DataFrame(preds, index=days, columns=["Predictions"]) i = 1 tablesToJoin = [] while i < self.walkForward.predictionPeriod: thisTable = predsTable.shift(i) thisTable.columns = ["Predictions_" + str(i)] tablesToJoin.append(thisTable) i += 1 predsTable = predsTable.join(tablesToJoin) transformedPreds = pd.DataFrame(predsTable.apply(lambda x:computePosition(x), axis=1), columns=["Predictions"]).dropna() dailyFactorReturn = getDailyFactorReturn(self.walkForward.targetTicker, dataOfInterest) transformedPreds = transformedPreds.join(dailyFactorReturn).dropna() returnStream = pd.DataFrame(transformedPreds.apply(lambda x:x[0] * x[1], axis=1), columns=["Algo Return"]) if returnStream is None else pd.concat([returnStream, pd.DataFrame(transformedPreds.apply(lambda x:x[0] * x[1], axis=1), columns=["Algo Return"])]) factorReturn = pd.DataFrame(transformedPreds[["Factor Return"]]) if factorReturn is None else pd.concat([factorReturn, pd.DataFrame(transformedPreds[["Factor Return"]])]) predictions = pd.DataFrame(transformedPreds[["Predictions"]]) if predictions is None else pd.concat([predictions, pd.DataFrame(transformedPreds[["Predictions"]])]) alpha, beta = empyrical.alpha_beta(returnStream, factorReturn) rawBeta = abs(empyrical.alpha_beta(returnStream.apply(lambda x:applyBinary(x), axis=0), factorReturn.apply(lambda x:applyBinary(x), axis=0))[1]) shortSharpe = empyrical.sharpe_ratio(returnStream) activity = np.count_nonzero(returnStream)/float(len(returnStream)) algoAnnualReturn = empyrical.annual_return(returnStream.values)[0] algoVol = empyrical.annual_volatility(returnStream.values) factorAnnualReturn = empyrical.annual_return(factorReturn.values)[0] factorVol = empyrical.annual_volatility(factorReturn.values) treynor = ((empyrical.annual_return(returnStream.values)[0] - empyrical.annual_return(factorReturn.values)[0]) \ / abs(empyrical.beta(returnStream, factorReturn))) sharpeDiff = empyrical.sharpe_ratio(returnStream) - empyrical.sharpe_ratio(factorReturn) relativeSharpe = sharpeDiff / empyrical.sharpe_ratio(factorReturn) * (empyrical.sharpe_ratio(factorReturn)/abs(empyrical.sharpe_ratio(factorReturn))) stability = empyrical.stability_of_timeseries(returnStream) ##CALCULATE SHARPE WITH SLIPPAGE estimatedSlippageLoss = portfolioGeneration.estimateTransactionCost(predictions) estimatedSlippageLoss.columns = returnStream.columns slippageAdjustedReturn = (returnStream - estimatedSlippageLoss).dropna() slippageSharpe = empyrical.sharpe_ratio(slippageAdjustedReturn) sharpeDiffSlippage = empyrical.sharpe_ratio(slippageAdjustedReturn) - empyrical.sharpe_ratio(factorReturn) relativeSharpeSlippage = sharpeDiffSlippage / empyrical.sharpe_ratio(factorReturn) * (empyrical.sharpe_ratio(factorReturn)/abs(empyrical.sharpe_ratio(factorReturn))) if (empyrical.sharpe_ratio(returnStream) < 0.0 or abs(beta) > 0.7 or activity < 0.5 or accuracy < 0.45) and shortSeen == 0: return None, { "sharpe":shortSharpe, ##OVERLOADED IN FAIL "factorSharpe":empyrical.sharpe_ratio(factorReturn), "sharpeSlippage":slippageSharpe, "beta":abs(beta), "alpha":alpha, "activity":activity, "treynor":treynor, "period":"first 252 days", "algoReturn":algoAnnualReturn, "algoVol":algoVol, "factorReturn":factorAnnualReturn, "factorVol":factorVol, "sharpeDiff":sharpeDiff, "relativeSharpe":relativeSharpe, "sharpeDiffSlippage":sharpeDiffSlippage, "relativeSharpeSlippage":relativeSharpeSlippage, "rawBeta":rawBeta, "stability":stability, "loss":loss, "roc_auc":roc_auc, "accuracy":accuracy }, None, None elif (((empyrical.sharpe_ratio(returnStream) < 0.25 or slippageSharpe < 0.0) and shortSeen == 1) or ((empyrical.sharpe_ratio(returnStream) < 0.25 or slippageSharpe < 0.0) and (shortSeen == 2 or shortSeen == 3)) or abs(beta) > 0.6 or activity < 0.6 or stability < 0.4 or accuracy < 0.45) and (shortSeen == 1 or shortSeen == 2 or shortSeen == 3): periodName = "first 600 days" if shortSeen == 2: periodName = "first 900 days" elif shortSeen == 3: periodName = "first 1200 days" return None, { "sharpe":shortSharpe, ##OVERLOADED IN FAIL "factorSharpe":empyrical.sharpe_ratio(factorReturn), "sharpeSlippage":slippageSharpe, "alpha":alpha, "beta":abs(beta), "activity":activity, "treynor":treynor, "period":periodName, "algoReturn":algoAnnualReturn, "algoVol":algoVol, "factorReturn":factorAnnualReturn, "factorVol":factorVol, "sharpeDiff":sharpeDiff, "relativeSharpe":relativeSharpe, "sharpeDiffSlippage":sharpeDiffSlippage, "relativeSharpeSlippage":relativeSharpeSlippage, "rawBeta":rawBeta, "stability":stability, "loss":loss, "roc_auc":roc_auc, "accuracy":accuracy }, None, None elif shortSeen < 4: print("CONTINUING", "SHARPE:", shortSharpe, "SHARPE DIFF:", sharpeDiff, "RAW BETA:", rawBeta, "TREYNOR:", treynor) shortSeen += 1 return returnStream, factorReturn, predictions, slippageAdjustedReturn