def get_tracking_error(strategy, benchmark, start_date, end_date) -> float: '''calculates the tracking error of a strategy compared to a benchmark''' assert check_prices(strategy=strategy, benchmark=benchmark) assert check_time(start_date=start_date, end_date=end_date) ann_ex_r = get_annualized_excess_return(strategy, benchmark, start_date, end_date) ir = get_information_ratio(strategy, benchmark, start_date, end_date) tracking_error = ann_ex_r / ir return tracking_error
def get_daily_returns(prices) -> pd.Series: '''calculates the daily returns of a prices time-series''' assert check_prices(prices=prices) prices.sort_index(inplace=True) # make sure dates is in ascending order daily_change = [0] for i in range(1, len(prices)): yesterday = prices[i - 1] today = prices[i] change = round((today - yesterday) / yesterday, 4) daily_change.append(change) daily_change = pd.Series(data=daily_change, index=prices.index) return daily_change
def get_risk_adjusted_return(prices, start_date, end_date) -> float: '''calculates the risk-adjusted return of a prices time-series between two dates''' assert check_prices(prices=prices) assert check_time(start_date=start_date, end_date=end_date) prices = prices.loc[start_date:end_date] start_date, end_date = prices.index[0], prices.index[-1] ann_rtn = get_annualized_return(prices, start_date, end_date) vo = get_strategy_volatility(prices, start_date, end_date) risk_adj_rtn = ann_rtn / vo risk_adj_rt = round(risk_adj_rtn, 4) return risk_adj_rt
def get_cumulative_return(prices, start_date, end_date) -> float: '''calculates the cumulative return between two dates''' assert check_prices(prices=prices) assert check_time(start_date=start_date, end_date=end_date) prices.sort_index(inplace=True) # make sure dates is in ascending order prices = prices.loc[start_date:end_date] start_date, end_date = prices.index[0], prices.index[-1] cost = prices[start_date] revenue = prices[end_date] cum_rtn = (revenue - cost) / cost cum_rtn = round(cum_rtn, 4) return cum_rtn
def get_strategy_volatility(prices, start_date, end_date) -> float: '''calculates the volatility of a prices time-series between two dates''' assert check_prices(prices=prices) assert check_time(start_date=start_date, end_date=end_date) change = prices.pct_change().dropna() change = change[change != 0] change_avg = np.mean(change) daily_sum = np.sum([(c - change_avg)**2 for c in change]) vo = np.sqrt((250 / ((end_date - start_date).days - 1)) * daily_sum) vo = round(vo, 4) return vo
def get_excess_return(strategy, benchmark, start_date, end_date) -> float: '''calculates the excess return of a strategy over a benchmark between two dates''' assert check_prices(strategy=strategy, benchmark=benchmark) assert check_time(start_date=start_date, end_date=end_date) strategy = strategy.loc[start_date:end_date] benchmark = benchmark.loc[start_date:end_date] start_date, end_date = strategy.index[0], strategy.index[-1] r_strategy = get_daily_returns(strategy) r_benchmark = get_daily_returns(benchmark) excess_return = (r_strategy - r_benchmark).cumsum() excess_return = round(excess_return, 4) return excess_return
def get_annualized_excess_return(strategy, benchmark, start_date, end_date) -> float: '''calculate the annuliazed return of a strategy compared to a benchmark''' assert check_prices(strategy=strategy, benchmark=benchmark) assert check_time(start_date=start_date, end_date=end_date) strategy = strategy.loc[start_date:end_date] benchmark = benchmark.loc[start_date:end_date] start_date, end_date = strategy.index[0], strategy.index[-1] strategy_return = get_annualized_return(strategy, start_date, end_date) market_return = get_annualized_return(benchmark, start_date, end_date) ann_ex_rtn = strategy_return - market_return ann_ex_rtn = round(ann_ex_rtn, 4) return ann_ex_rtn
def get_sharpe_ratio(prices, start_date, end_date, risk_free=0.04) -> float: '''calculates the sharpe ratio of a prices time-series between two dates''' assert check_prices(prices=prices) assert check_time(start_date=start_date, end_date=end_date) assert 0 <= risk_free <= 1, 'the risk free rate must be between 0 and 1' prices = prices.loc[start_date:end_date] start_date, end_date = prices.index[0], prices.index[-1] ann_rtn = get_annualized_return(prices, start_date, end_date) excess_rtn = ann_rtn - risk_free vo = get_strategy_volatility(prices, start_date, end_date) sharpe_ratio = excess_rtn / vo sharpe_ratio = round(sharpe_ratio, 4) return sharpe_ratio
def get_max_drawdown(prices, start_date, end_date) -> float: '''calculates the maximum drawdown of a prices time-series between two dates''' assert check_prices(prices=prices) assert check_time(start_date=start_date, end_date=end_date) prices = prices.loc[start_date:end_date] start_date, end_date = prices.index[0], prices.index[-1] max_drawdown = 0 # for each day, get the lowest price in the period after for day in prices.index: day_price = prices[day] lowest = prices.loc[day:].min() drawdown = (day_price - lowest) / day_price if drawdown > max_drawdown: max_drawdown = drawdown max_drawdown = round(max_drawdown, 4) return max_drawdown
def get_beta(strategy, benchmark, start_date, end_date) -> float: '''calculates the beta of a prices time-series between two dates''' assert check_prices(strategy=strategy, benchmark=benchmark) assert check_time(start_date=start_date, end_date=end_date) strategy = strategy.loc[start_date:end_date] benchmark = benchmark.loc[start_date:end_date] start_date, end_date = strategy.index[0], strategy.index[-1] r_strategy = get_daily_returns(strategy) r_benchmark = get_daily_returns(benchmark) var = np.var(r_benchmark, ddof=1) cov = np.cov(r_strategy, r_benchmark)[0][1] beta = cov / var beta = round(beta, 4) return beta
def get_information_ratio(strategy, benchmark, start_date, end_date) -> float: '''calculates the information ratio of a prices time-series between two dates''' assert check_prices(strategy=strategy, benchmark=benchmark) assert check_time(start_date=start_date, end_date=end_date) excess_return = get_annualized_excess_return(strategy, benchmark, start_date, end_date) strategy_daily = get_daily_returns(strategy) benchmark_daily = get_daily_returns(benchmark) strategy_daily = strategy_daily[strategy_daily != 0] benchmark_daily = benchmark_daily[benchmark_daily != 0] daily_excess_return = strategy_daily - benchmark_daily daily_stdev = np.std(daily_excess_return) * np.sqrt(250) ir = excess_return / daily_stdev ir = round(ir, 4) return ir
def get_daily_win_rate(strategy, benchmark, start_date, end_date) -> float: '''calculates the daily win rate of a strategy compared to a benchmark''' assert check_prices(strategy=strategy, benchmark=benchmark) assert check_time(start_date=start_date, end_date=end_date) strategy = strategy.loc[start_date:end_date] benchmark = benchmark.loc[start_date:end_date] start_date, end_date = strategy.index[0], strategy.index[-1] r_strategy = get_daily_returns(strategy) r_benchmark = get_daily_returns(benchmark) daily_diff = r_strategy - r_benchmark win = 0 for day in daily_diff: if day > 0: win += 1 daily_win_rate = win / len(daily_diff) daily_win_rate = round(daily_win_rate, 4) return daily_win_rate
def get_alpha(strategy, benchmark, start_date, end_date, risk_free=0.04) -> float: '''calculates the alpha of a prices time-series between two dates''' assert check_prices(strategy=strategy, benchmark=benchmark) assert check_time(start_date=start_date, end_date=end_date) assert 0 <= risk_free <= 1, 'the risk free rate must be between 0 and 1' strategy = strategy.loc[start_date:end_date] benchmark = benchmark.loc[start_date:end_date] start_date, end_date = strategy.index[0], strategy.index[-1] market_return = get_annualized_return(benchmark, start_date, end_date) beta = get_beta(strategy, benchmark, start_date, end_date) capm = risk_free + beta * (market_return - risk_free ) # asset price under the CAPM model annualized_return = get_annualized_return(strategy, start_date, end_date) alpha = annualized_return - capm alpha = round(alpha, 4) return alpha
def show_metrics(strategy, benchmark, holdings, df_price): ''' prints out a chart that shows columns of metrics Parameters ---------- strategy : pandas.Series the performance of the strategy being backtested benchmark : pandas.Series the performance of the benchmark used in backtest ''' assert check_prices(strategy=strategy, benchmark=benchmark) start_date = strategy.index[0] end_date = strategy.index[-1] benchmark = benchmark.loc[start_date:end_date] date_range = pd.date_range(start=start_date, end=end_date) benchmark = benchmark.reindex(date_range, method='ffill') benchmark.fillna(method='bfill', inplace=True) cum_r = get_cumulative_return(strategy, start_date, end_date) ann_r = get_annualized_return(strategy, start_date, end_date) ann_ex_r = get_annualized_excess_return(strategy, benchmark, start_date, end_date) max_dd = get_max_drawdown(strategy, start_date, end_date) vo = get_strategy_volatility(strategy, start_date, end_date) risk_adj = get_risk_adjusted_return(strategy, start_date, end_date) sharpe = get_sharpe_ratio(strategy, start_date, end_date) ir = get_information_ratio(strategy, benchmark, start_date, end_date) beta = get_beta(strategy, benchmark, start_date, end_date) alpha = get_alpha(strategy, benchmark, start_date, end_date) win_r = get_win_rate(start_date, end_date, holdings, df_price) win_r_d = get_daily_win_rate(strategy, benchmark, start_date, end_date) pl = get_pl_ratio(start_date, end_date, holdings, df_price) to_r = get_turnover_ratio(start_date, end_date, holdings, df_price) trk_err = get_tracking_error(strategy, benchmark, start_date, end_date) print('\n============================================') print('| Key Metrics ') print('============================================') print(f'| Start Date: {start_date.date()}') print(f'| End Date: {end_date.date()}') print('============================================') print(f'| Cumulative Return: {round(cum_r*100, 2)}%') print(f'| Annualized Return: {round(ann_r*100, 2)}%') print(f'| Annualized Excess: {round(ann_ex_r*100, 2)}%') print(f'| Maximum Drawdown: {round(max_dd*100, 2)}%') print('============================================') print(f'| Risk Adjusted: {round(risk_adj, 3)}') print(f'| Information Ratio: {round(ir, 3)}') print(f'| Sharpe Ratio: {round(sharpe, 3)}') print(f'| Volatility: {round(vo, 3)}') print('============================================') print(f'| Alpha: {round(alpha, 3)}') print(f'| Beta: {round(beta, 3)}') print('============================================') print(f'| Win Rate: {round(win_r*100, 2)}%') print(f'| Daily Win Rate: {round(win_r_d*100, 2)}%') print(f'| Profit-Loss Ratio: {round(pl, 1)} : 1') print('============================================') print(f'| Turnover Ratio: {round(to_r*100, 2)}%') print(f'| Tracking Error: {round(trk_err*100, 2)}%') print('============================================')