def get_factor_return_attribution(cls, fund_tms: QFSeries, fit_tms: QFSeries, regressors_df: QFDataFrame, coefficients: QFSeries, alpha: float) -> Tuple[QFSeries, float]: """ Returns performance attribution for each factor in given regressors and also calculates the unexplained return. """ fund_returns = fund_tms.to_simple_returns() regressors_returns = regressors_df.to_simple_returns() annualised_fund_return = cagr(fund_returns) annualised_fit_return = cagr(fit_tms) total_nav = fit_tms.to_prices(initial_price=1.0) def calc_factors_profit(series) -> float: factor_ret = regressors_returns.loc[:, series.name].values return coefficients.loc[series.name] * (total_nav[:-1].values * factor_ret).sum() factors_profits = regressors_returns.apply(calc_factors_profit) alpha_profit = total_nav[:-1].sum() * alpha total_profit = factors_profits.sum() + alpha_profit regressors_return_attribution = factors_profits * annualised_fit_return / total_profit regressors_return_attribution = cast_series( regressors_return_attribution, QFSeries) unexplained_return = annualised_fund_return - regressors_return_attribution.sum( ) return regressors_return_attribution, unexplained_return
def sharpe_ratio(qf_series: QFSeries, frequency: Frequency, risk_free: float = 0) -> float: """ Calculates the Sharpe Ratio for a given timeseries of returns and given frequency. Parameters ---------- qf_series: QFSeries financial series frequency: Frequency frequency of the series risk_free: float risk free rate Returns ------- float Sharpe Ratio for given series and frequency """ annual_simple_return = cagr(qf_series, frequency) annual_log_return = np.log(annual_simple_return + 1) annual_vol = get_volatility(qf_series, frequency, annualise=True) return (annual_log_return - risk_free) / annual_vol
def sorino_ratio(qf_series: QFSeries, frequency: Frequency, risk_free: float = 0) -> float: """ Calculates the Sorino ratio for a given timeseries of returns. sorino_ratio = (CAGR - risk free) / annualised downside volatility Parameters ---------- qf_series: QFSeries financial series frequency: Frequency frequency of the qf_series risk_free: float risk free rate Returns ------- float """ annualised_growth_rate = cagr(qf_series, frequency) negative_returns = qf_series[qf_series < 0] annualised_downside_vol = get_volatility(negative_returns, frequency, annualise=True) ratio = (annualised_growth_rate - risk_free) / annualised_downside_vol return ratio
def _get_best_strategies_returns(self) -> List[float]: """ Returns the annual returns of the best IS strategies """ annual_returns = [] for oos_set, best_strategy_name in zip(self._oos_set, self.best_is_strategies_names): best_strategy_tms = oos_set.loc[:, best_strategy_name] annual_simple_return = cagr(best_strategy_tms, Frequency.DAILY) annual_returns.append(annual_simple_return) return annual_returns
def evaluate_params_for_tickers(self, parameters: tuple, tickers: Sequence[Ticker], start_time: datetime, end_date: datetime): # Get the backtest element for the given list of tickers backtest_elements_for_tickers = [ el for el in self.params_backtest_summary_elem_dict[parameters] if set(el.tickers) == set(tickers) ] assert len(backtest_elements_for_tickers) == 1, "Check if the modeled_params passed to " \ "FastAlphaModelTesterConfig match those you want to test" backtest_elem = backtest_elements_for_tickers[0] returns_tms = backtest_elem.returns_tms.dropna(how="all") trades = backtest_elem.trades # Create the TradesEvaluationResult object ticker_evaluation = TradesEvaluationResult() ticker_evaluation.ticker = tickers ticker_evaluation.parameters = parameters ticker_evaluation.end_date = end_date # Compute the start date as the maximum value between the given start_time and the first date of returns tms in # case of 1 ticker backtest if len(tickers) == 1: start_date = max( start_time, returns_tms.index[0]) if not returns_tms.empty else end_date else: start_date = start_time ticker_evaluation.start_date = start_date if start_date >= end_date: # Do not compute further fields - return the default None values return ticker_evaluation avg_nr_of_trades = avg_nr_of_trades_per1y( QFSeries([ t for t in trades if t.start_time >= start_date and t.end_time <= end_date ]), start_date, end_date) ticker_evaluation.avg_nr_of_trades_1Y = avg_nr_of_trades ticker_evaluation.sqn_per_avg_nr_trades = sqn( QFSeries([ t.pnl for t in trades if t.start_time >= start_date and t.end_time <= end_date ])) * sqrt(avg_nr_of_trades) returns_tms = returns_tms.loc[start_date:end_date] if not returns_tms.empty: ticker_evaluation.annualised_return = cagr(returns_tms, Frequency.DAILY) return ticker_evaluation
def trade_based_cagr(trades: QFDataFrame, start_date: datetime, end_date: datetime): """ Calculates average number of trades per year for a given data-frame of trades. """ returns = trades[TradeField.Return] dates = trades[TradeField.EndDate] # insert start date and the beginning and end date at the end. # we insert nex start + 1day to returns and set the frequency for to_prices to daily so that the # prices series will start exactly from the start_date returns = pd.concat([pd.Series([0]), returns, pd.Series([0])]) dates = pd.concat([ pd.Series([start_date]), dates + timedelta(days=1), pd.Series([end_date]) ]) returns_tms = SimpleReturnsSeries(index=dates, data=returns.values) prices_tms = returns_tms.to_prices(frequency=Frequency.DAILY) return cagr(prices_tms)
def calmar_ratio(qf_series: QFSeries, frequency: Frequency) -> float: """ Calculates the Calmar ratio for a given timeseries of returns. calmar_ratio = CAGR / max drawdown Parameters ---------- qf_series financial series frequency frequency of qf_series Returns ------- calmar_ratio """ annualised_growth_rate = cagr(qf_series, frequency) max_dd = max_drawdown(qf_series) ratio = annualised_growth_rate / max_dd return ratio
def _calculate_return(self): self.total_return = self.returns_tms.total_cumulative_return() self.cagr = cagr(self.returns_tms, self.frequency)
def test_compound_annual_growth_rate_monthly(self): actual_return = cagr(self.test_dd_prices_tms) expected_return = pow(1.5, 12.0/12.0)-1 self.assertAlmostEqual(expected_return, actual_return, delta=0.001)
def test_compound_annual_growth_rate(self): expected_return = 535.67015428006502590536838042708 actual_return = cagr(self.test_simple_returns_tms) self.assertAlmostEqual(expected_return, actual_return)