def _try_get_freq(self): if self.freq is None: freq = pd.infer_freq(self.index) if freq is None: raise FrequencyError('No frequency was passed at' ' instantiation, and one cannot' ' be inferred.') freq = utils.get_anlz_factor(freq) else: freq = utils.get_anlz_factor(self.freq) return freq
def amortize(rate, nper, pv, freq="M"): """Construct an amortization schedule for a fixed-rate loan. Rate -> annualized input Example ------- # a 6.75% $200,000 loan, 30-year tenor, payments due monthly # view the 5 final months print(amortize(rate=.0675, nper=30, pv=200000).round(2).tail()) beg_bal prin interest end_bal 356 6377.95 -1261.32 -35.88 5116.63 357 5116.63 -1268.42 -28.78 3848.22 358 3848.22 -1275.55 -21.65 2572.67 359 2572.67 -1282.72 -14.47 1289.94 360 1289.94 -1289.94 -7.26 -0.00 """ freq = utils.get_anlz_factor(freq) rate = rate / freq nper = nper * freq periods = np.arange(1, nper + 1, dtype=int) principal = np.ppmt(rate, periods, nper, pv) interest = np.ipmt(rate, periods, nper, pv) pmt = np.pmt(rate, nper, pv) def balance(pv, rate, nper, pmt): dfac = (1 + rate) ** nper return pv * dfac - pmt * (dfac - 1) / rate res = pd.DataFrame( { "beg_bal": balance(pv, rate, periods - 1, -pmt), "prin": principal, "interest": interest, "end_bal": balance(pv, rate, periods, -pmt), }, index=periods, )["beg_bal", "prin", "interest", "end_bal"] return res
def anlzd_ret(self, freq=None): """Annualized (geometric) return. Parameters ---------- freq : str or None, default None A frequency string used to create an annualization factor. If None, `self.freq` will be used. If that is also None, a frequency will be inferred. If none can be inferred, an exception is raised. It may be any frequency string or anchored offset string recognized by Pandas, such as 'D', '5D', 'Q', 'Q-DEC', or 'BQS-APR'. Returns ------- float """ if self.index.is_all_dates: # TODO: Could be more granular here, # for cases with < day frequency. td = self.index[-1] - self.index[0] n = td.total_seconds() / SECS_PER_CAL_YEAR else: # We don't have a datetime-like Index, so assume # periods/dates are consecutive and simply count them. # We do, however, need an explicit frequency. freq = freq if freq is not None else self.freq if freq is None: raise FrequencyError('Must specify a `freq` when a' ' datetime-like index is not used.') n = len(self) / utils.get_anlz_factor(freq) return nanprod(self.ret_rels())**(1. / n) - 1.
def __init__( self, r, fee=0.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.get_anlz_factor(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.0: "is_month_end", 4.0: "is_quarter_end", 1.0: "is_quarter_end", } mask = getattr(self.index, masktypes[self.fee_freq]) self.feesched = np.where(mask, self.fee, 0.0) # Net of fees (not yet of distributions) self.net = (1.0 + self.gross.values) * ( 1.0 - self.feesched.reshape(-1, 1) ) - 1.0 self.net = pd.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