def compute_num_periods(timestamps: np.ndarray, periods_per_year: float) -> float: ''' Given an array of timestamps, we compute how many periods there are between the first and last element, where the length of a period is defined by periods_per_year. For example, if there are 6 periods per year, then each period would be approx. 2 months long. Args: timestamps (np.ndarray of np.datetime64): a numpy array of returns, can contain nans periods_per_year: number of periods between first and last return >>> assert(compute_num_periods(np.array(['2015-01-01', '2015-03-01', '2015-05-01'], dtype='M8[D]'), 6) == 2) ''' if not len(timestamps): return np.nan assert (monotonically_increasing(timestamps)) fraction_of_year = (timestamps[-1] - timestamps[0]) / ( np.timedelta64(1, 's') * 365 * 24 * 60 * 60) return round(fraction_of_year * periods_per_year)
def compute_return_metrics( timestamps: np.ndarray, rets: np.ndarray, starting_equity: float, leading_non_finite_to_zeros: bool = False, subsequent_non_finite_to_zeros: bool = True) -> Evaluator: ''' Compute a set of common metrics using returns (for example, of an instrument or a portfolio) Args: timestamps (np.array of datetime64): Timestamps for the returns rets (nd.array of float): The returns, use 0.01 for 1% starting_equity (float): Starting equity value in your portfolio leading_non_finite_to_zeros (bool, optional): If set, we replace leading nan, inf, -inf returns with zeros. For example, you may need a warmup period for moving averages. Default False subsequent_non_finite_to_zeros (bool, optional): If set, we replace any nans that follow the first non nan value with zeros. There may be periods where you have no prices but removing these returns would result in incorrect annualization. Default True Returns: An Evaluator object containing computed metrics off the returns passed in. If needed, you can add your own metrics to this object based on the values of existing metrics and recompute the Evaluator. Otherwise, you can just use the output of the evaluator using the metrics function. >>> timestamps = np.array(['2015-01-01', '2015-03-01', '2015-05-01', '2015-09-01'], dtype='M8[D]') >>> rets = np.array([0.01, 0.02, np.nan, -0.015]) >>> starting_equity = 1.e6 >>> ev = compute_return_metrics(timestamps, rets, starting_equity) >>> metrics = ev.metrics() >>> assert(round(metrics['gmean'], 6) == 0.021061) >>> assert(round(metrics['sharpe'], 6) == 0.599382) >>> assert(all(metrics['returns_3yr'] == np.array([0.01, 0.02, 0, -0.015]))) ''' assert (starting_equity > 0.) assert (type(rets) == np.ndarray and rets.dtype == np.float64) assert (type(timestamps) == np.ndarray and np.issubdtype(timestamps.dtype, np.datetime64) and monotonically_increasing(timestamps)) timestamps, rets = handle_non_finite_returns( timestamps, rets, leading_non_finite_to_zeros, subsequent_non_finite_to_zeros) ev = Evaluator({ 'timestamps': timestamps, 'returns': rets, 'starting_equity': starting_equity }) ev.add_metric('periods_per_year', compute_periods_per_year, dependencies=['timestamps']) ev.add_metric('amean', compute_amean, dependencies=['returns', 'periods_per_year']) ev.add_metric('std', compute_std, dependencies=['returns']) ev.add_metric('up_periods', lambda returns: len(returns[returns > 0]), dependencies=['returns']) ev.add_metric('down_periods', lambda returns: len(returns[returns < 0]), dependencies=['returns']) ev.add_metric('up_pct', lambda up_periods, down_periods: up_periods * 1.0 / (up_periods + down_periods) if (up_periods + down_periods) != 0 else np.nan, dependencies=['up_periods', 'down_periods']) ev.add_metric('gmean', compute_gmean, dependencies=['timestamps', 'returns', 'periods_per_year']) ev.add_metric('sharpe', compute_sharpe, dependencies=['returns', 'periods_per_year', 'amean']) ev.add_metric('sortino', compute_sortino, dependencies=['returns', 'periods_per_year', 'amean']) ev.add_metric('equity', compute_equity, dependencies=['timestamps', 'starting_equity', 'returns']) ev.add_metric('k_ratio', compute_k_ratio, dependencies=['equity', 'periods_per_year']) ev.add_metric('k_ratio_weighted', lambda equity, periods_per_year: compute_k_ratio( equity, periods_per_year, 3), dependencies=['equity', 'periods_per_year']) # Drawdowns ev.add_metric('rolling_dd', compute_rolling_dd, dependencies=['timestamps', 'equity']) ev.add_metric('mdd_pct', lambda rolling_dd: compute_maxdd_pct(rolling_dd[1]), dependencies=['rolling_dd']) ev.add_metric( 'mdd_date', lambda rolling_dd: compute_maxdd_date(rolling_dd[0], rolling_dd[1]), dependencies=['rolling_dd']) ev.add_metric('mdd_start', lambda rolling_dd, mdd_date: compute_maxdd_start( rolling_dd[0], rolling_dd[1], mdd_date), dependencies=['rolling_dd', 'mdd_date']) ev.add_metric('mar', compute_mar, dependencies=['returns', 'periods_per_year', 'mdd_pct']) ev.add_metric('timestamps_3yr', compute_dates_3yr, dependencies=['timestamps']) ev.add_metric('returns_3yr', compute_returns_3yr, dependencies=['timestamps', 'returns']) ev.add_metric('rolling_dd_3yr', compute_rolling_dd_3yr, dependencies=['timestamps', 'equity']) ev.add_metric( 'mdd_pct_3yr', lambda rolling_dd_3yr: compute_maxdd_pct_3yr(rolling_dd_3yr[1]), dependencies=['rolling_dd_3yr']) ev.add_metric('mdd_date_3yr', lambda rolling_dd_3yr: compute_maxdd_date_3yr( rolling_dd_3yr[0], rolling_dd_3yr[1]), dependencies=['rolling_dd_3yr']) ev.add_metric('mdd_start_3yr', lambda rolling_dd_3yr, mdd_date_3yr: compute_maxdd_start_3yr( rolling_dd_3yr[0], rolling_dd_3yr[1], mdd_date_3yr), dependencies=['rolling_dd_3yr', 'mdd_date_3yr']) ev.add_metric( 'calmar', compute_calmar, dependencies=['returns_3yr', 'periods_per_year', 'mdd_pct_3yr']) ev.add_metric('annual_returns', compute_annual_returns, dependencies=['timestamps', 'returns', 'periods_per_year']) ev.add_metric('bucketed_returns', compute_bucketed_returns, dependencies=['timestamps', 'returns']) ev.compute() return ev