Example #1
0
 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
Example #2
0
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
Example #3
0
    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.
Example #4
0
    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