def __init__(self, r, fee=0., fee_freq='Q', start=None, end=None, lookback={}, strict=False, dist_amt=None, dist_pct=None, dist_freq=None, v0=float(1e6), include_start=True, freq='M', name=None, in_format='num'): self.gross = returns.prep(r=r, freq=freq, name=name, in_format=in_format) # fee_freq: if str -> frequency; if int/float -> periods/yr # Get `fee` to a per-period float if isinstance(fee_freq, (int, float)): self.fee_freq = fee_freq self.fee = fee / self.fee_freq elif isinstance(fee_freq, str): self.fee_freq = utils.convertfreq(fee_freq) self.fee = fee / self.fee_freq # Logic for interaction of `start`, `end`, and `lookback` # TODO: how should lookback be passed? Consider params to # `constrain_horizon` if any((start, end)) and not lookback: self.gross = self.gross[start:end] elif lookback: # TODO: cleanup self.gross = utils.constrain_horizon(self.gross, **lookback) elif all((any((start, end)), lookback)): raise ValueError('if `lookback` is specified, both `start` and' ' `end` should be None') self.index = self.gross.index self.columns = self.gross.columns masktypes = {12. : 'is_month_end', 4. : 'is_quarter_end', 1. : 'is_quarter_end'} mask = getattr(self.index, masktypes[self.fee_freq]) self.feesched = np.where(mask, self.fee, 0.) # Net of fees (not yet of distributions) self.net = (1. + self.gross.values) \ * (1. - self.feesched.reshape(-1,1)) - 1. self.net = DataFrame(self.net, index=self.index, columns=self.columns) self.dist_amt = dist_amt self.dist_pct = dist_pct self.dist_freq = dist_freq self.v0 = v0 self.include_start = include_start
def load_rf( freq='M', pickle_from=None, pickle_to=None, ): """Build a risk-free rate return series using 3-month US T-bill yields. The 3-Month Treasury Bill: Secondary Market Rate from the Federal Reserve (a yield) is convert to a total return. See 'Methodology' for details. The time series should closely mimic returns of the BofA Merrill Lynch US Treasury Bill (3M) (Local Total Return) index. Parameters ========== reload : bool, default False If False, use pickled data. If True, reload from source freq : str, sequence, or set If a single-character string, return a single-column DataFrame with index frequency corresponding to `freq`. If a sequence or set, return a dict of DataFrames with the keys corresponding to `freq`(s) Methodology =========== The Federal Reserve publishes a daily chart of Selected Interest Rates (release H.15; www.federalreserve.gov/releases/h15/). As with a yield curve, some yields are interpolated from recent issues because Treasury auctions do not occur daily. While the de-annualized ex-ante yield itself is a fairly good tracker of the day's total return, it is not perfect and can exhibit non-neglible error in periods of volatile short rates. The purpose of this function is to convert yields to total returns for 3-month T-bills. It is a straightforward process given that these are discount (zero-coupon) securities. It consists of buying a 3-month bond at the beginning of each month, then amortizing that bond throughout the month to back into the price of a <3-month tenor bond. The source data (pulled from fred.stlouisfed.org) is quoted on a discount basis. (See footnote 4 from release H.15.) This is converted to a bond-equivlanet yield (BEY) and then translated to a hypothetical daily total return. The process largely follows Morningstar's published Return Calculation of U.S. Treasury Constant Maturity Indices, and is as follows: - At the beginning of each month a bill is purchased at the prior month-end price, and daily returns in the month reflect the change in daily valuation of this bill - If t is not a business day, its yield is the yield of the prior business day. - At each day during the month, the price of a 3-month bill purchased on the final calendar day of the previous month is computed. - Month-end pricing is unique. At each month-end date, there are effectively two bonds and two prices. The first is the bond hypothetically purchased on the final day of the prior month with 2m remaining to maturity, and the second is a new-issue bond purchased that day with 3m to maturity. The former is used as the numerator to compute that day's total return, while the latter is used as the denominator to compute the next day's (1st day of next month) total return. Description of the BofA Merrill Lynch US 3-Month Treasury Bill Index: The BofA Merrill Lynch US 3-Month Treasury Bill Index is comprised of a single issue purchased at the beginning of the month and held for a full month. At the end of the month that issue is sold and rolled into a newly selected issue. The issue selected at each month-end rebalancing is the outstanding Treasury Bill that matures closest to, but not beyond, three months from the rebalancing date. To qualify for selection, an issue must have settled on or before the month-end rebalancing date. (Source: Bank of America Merrill Lynch) See also ======== FRED: 3-Month Treasury Bill: Secondary Market Rate (DTB3) https://fred.stlouisfed.org/series/DTB3 McGraw-Hill/Irwin, Interest Rates, 2008. https://people.ucsc.edu/~lbaum/econ80h/LS-Chap009.pdf Morningstar, Return Calculation of U.S. Treasury Constant Maturity Indices, September 2008. """ # Validate `freq` param freqs = list('DWMQA') freq = freq.upper() if freq.islower() else freq if freq not in freqs: raise ValueError('`freq` must be either a single element or subset' ' from %s, case-insensitive' % freqs) # Load daily 3-Month Treasury Bill: Secondary Market Rate # Note that this is on discount basis and will be converted to BEY # Periodicity is daily rates = dr('DTB3', 'fred', DSTART) * 0.01 rates = (rates.asfreq('D', method='ffill').fillna(method='ffill').squeeze()) # Algebra doesn't 'work' on DateOffsets, don't simplify here! trigger = rates.index.is_month_end dtm_old = rates.index + offsets.MonthEnd(-1) + offsets.MonthEnd(3) \ - rates.index dtm_new = rates.index.where(trigger, rates.index + offsets.MonthEnd(-1)) \ + offsets.MonthEnd(3) - rates.index # This does 2 things in one step: # (1) convert discount yield to BEY # (2) get the price at that BEY and days to maturity # The two equations are simplified # See https://people.ucsc.edu/~lbaum/econ80h/LS-Chap009.pdf p_old = (100 / 360) * (360 - rates * dtm_old.days) p_new = (100 / 360) * (360 - rates * dtm_new.days) res = p_old.pct_change().where(trigger, p_new.pct_change()) res = returns.prep(res, in_format='dec', name='RF', freq='D') if freq != 'D': res = returns.prep(dr.rollup(out_freq=freq), in_format='dec', freq=freq) return res