def test_broadcast_decimal(self): # Use almost equal because precision is tested in the explicit tests, # this test is to ensure broadcast with Decimal is not broken. assert_almost_equal(npf.ppmt(Decimal('0.1') / Decimal('12'), list(range(1, 5)), Decimal('24'), Decimal('2000')), [Decimal('-75.62318601'), Decimal('-76.25337923'), Decimal('-76.88882405'), Decimal('-77.52956425')], 4) result = npf.ppmt( Decimal('0.1') / Decimal('12'), list(range(1, 5)), Decimal('24'), Decimal('2000'), Decimal('0'), [Decimal('0'), Decimal('1'), 'end', 'begin'] ) desired = [ Decimal('-75.62318601'), Decimal('-75.62318601'), Decimal('-76.88882405'), Decimal('-76.88882405') ] assert_almost_equal(result, desired, decimal=4)
def test_when_is_end(self, when): args = (0.1 / 12, 1, 60, 55000, 0) result = npf.ppmt(*args) if when is None else npf.ppmt(*args, when) assert_allclose( result, -710.254126, # Computed using Google Sheet's PPMT rtol=1e-9, )
def test_when_is_end_decimal(self, when): args = (Decimal('0.08') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('15000.'), Decimal('0')) result = npf.ppmt(*args) if when is None else npf.ppmt(*args, when) assert_almost_equal( result, Decimal('-204.145914'), # Computed using Google Sheet's PPMT decimal=5, )
def test_broadcast(self): assert_almost_equal(npf.nper(0.075, -2000, 0, 100000., [0, 1]), [21.5449442, 20.76156441], 4) assert_almost_equal(npf.ppmt(0.1 / 12, list(range(5)), 24, 2000), [numpy.nan, -75.62318601, -76.25337923, -76.88882405, -77.52956425], 4) assert_almost_equal(npf.ppmt(0.1 / 12, list(range(5)), 24, 2000, 0, [0, 0, 1, 'end', 'begin']), [numpy.nan, -75.62318601, -75.62318601, -76.88882405, -76.88882405], 4)
def raise_error_because_not_equal(): assert_equal( round( npf.ppmt( Decimal('0.23') / Decimal('12'), 1, 60, Decimal('10000000000')), 8), Decimal('-90238044.232277036'))
def amortization_schedule(input_intrate, mortgage, years, down_paymnt): ##### PARAMETERS ##### # CONVERT MORTGAGE AMOUNT TO NEGATIVE BECAUSE MONEY IS GOING OUT down = down_payment(mortgage, down_paymnt) loan = mortgage - down mortgage_amount = -(loan) interest_rate = (input_intrate / 100) / 12 periods = years * 12 # CREATE ARRAY n_periods = np.arange(years * 12) + 1 ##### BUILD AMORTIZATION SCHEDULE ##### # INTEREST PAYMENT interest_monthly = npf.ipmt(interest_rate, n_periods, periods, mortgage_amount) # PRINCIPAL PAYMENT principal_monthly = npf.ppmt(interest_rate, n_periods, periods, mortgage_amount) # JOIN DATA df_initialize = list(zip(n_periods, interest_monthly, principal_monthly)) df = pd.DataFrame(df_initialize, columns=['Period', 'Interest', 'Principal']) # MONTHLY MORTGAGE PAYMENT df['Monthly Payment'] = df['Interest'] + df['Principal'] # CALCULATE CUMULATIVE SUM OF MORTAGE PAYMENTS df['Balance'] = df['Monthly Payment'].cumsum() # REVERSE VALUES SINCE WE ARE PAYING DOWN THE BALANCE df.Balance = df.Balance.values[::-1] return df
def mortgage_monthly(price, years, percent, down_paymnt): # THIS IMPLEMENTS AN APPROACH TO FINDING A MONTHLY MORTGAGE AMOUNT FROM THE PURCHASE PRICE, # YEARS, INTEREST RATE AND DOWN PAYMENT ##### PARAMETERS ##### down = down_payment(price, down_paymnt) loan = price - down mortgage_amount = -loan interest_rate = (percent / 100) / 12 periods = years * 12 # CREATE ARRAY n_periods = np.arange(years * 12) + 1 ##### BUILD AMORTIZATION SCHEDULE ##### # INTEREST PAYMENT interest_monthly = npf.ipmt(interest_rate, n_periods, periods, mortgage_amount) # PRINCIPAL PAYMENT principal_monthly = npf.ppmt(interest_rate, n_periods, periods, mortgage_amount) # JOIN DATA df_initialize = list(zip(n_periods, interest_monthly, principal_monthly)) df = pd.DataFrame(df_initialize, columns=['Period', 'Interest', 'Principal']) # MONTHLY MORTGAGE PAYMENT df['Monthly Payment'] = df['Interest'] + df['Principal'] payment = df['Monthly Payment'].mean() return payment
def test_decimal(self): result = npf.ppmt( Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000')) assert_equal( result, Decimal('-710.2541257864217612489830917'), )
def get_planned_payments_at(self, date): if before(self.date, date): return 0.0 initial_date = self.date + relativedelta(days=10) num_intervals = get_interval_in_months(initial_date, date) total_paid = 0 for p in range(num_intervals): total_paid += npf.ppmt(self.rate, p + 1, self.periods, self.amount) return total_paid
def test_when_is_begin_decimal(self, when): result = npf.ppmt( Decimal('0.08') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('15000.'), Decimal('0'), when) assert_almost_equal( result, Decimal('-302.131703'), # Computed using Google Sheet's PPMT decimal=5, )
def principal_remaining_after(p, n, r, rem_years): rr = r / 1200.0 nn = n*12 rem_mths = rem_years * 12 paid_mths = nn - rem_mths paid_prin = 0.0 for i in range(1,paid_mths+1): pp = npf.ppmt(rr, i, nn, p) paid_prin += pp return (p*-1) - paid_prin
def test_broadcast(self): assert_almost_equal(npf.nper(0.075, -2000, 0, 100000., [0, 1]), [21.5449442, 20.76156441], 4) assert_almost_equal(npf.ipmt(0.1 / 12, list(range(5)), 24, 2000), [ -17.29165168, -16.66666667, -16.03647345, -15.40102862, -14.76028842 ], 4) assert_almost_equal(npf.ppmt(0.1 / 12, list(range(5)), 24, 2000), [ -74.998201, -75.62318601, -76.25337923, -76.88882405, -77.52956425 ], 4) assert_almost_equal( npf.ppmt(0.1 / 12, list(range(5)), 24, 2000, 0, [0, 0, 1, 'end', 'begin']), [ -74.998201, -75.62318601, -75.62318601, -76.88882405, -76.88882405 ], 4)
def calcLoan(self, loan=0.6, interest=0.04, years=10): quantity = loan * self.CAPEX assert quantity > 0 assert interest >= 0 and interest <= 1 assert years > 1 self.loan_payment = pmt(interest, years, quantity) self.loan_interest = ipmt(interest, np.arange(years) + 1, years, quantity) self.loan_principal = ppmt(interest, np.arange(years) + 1, years, quantity)
def _get_mortgage_balance( mortgage: Dict[str, Union[int, float]]) -> np.ndarray: """ Returns array of outstanding mortgage balance for each month, from dict of mortgage information. Args: mortgage: dict with keys 'mortgage_size', 'term', 'offer_term', 'interest_rate' and 'offer_rate' Returns: array of outstanding mortgage balances """ # Get variables mortgage_size = int(mortgage.get("mortgage_size")) * 1000 term = mortgage.get("term") * 12 offer_term = mortgage.get("offer_term") * 12 remaining_term = term - offer_term interest_rate = (mortgage.get("interest_rate") / 12) / 100 offer_rate = (mortgage.get("offer_rate") / 12) / 100 # monthly interest rate # Get offer principal payments offer_per = np.arange(term) + 1 offer_principal_payments = ( -1 * npf.ppmt(offer_rate, offer_per, term, mortgage_size))[:offer_term] # Get regular principal payments balance_after_offer = mortgage_size - np.sum(offer_principal_payments) remaining_per = np.arange(remaining_term) + 1 remaining_principal_payments = -1 * npf.ppmt( interest_rate, remaining_per, remaining_term, balance_after_offer) principal_payments = np.append(offer_principal_payments, remaining_principal_payments) # Get outstanding balance outstanding_balance = mortgage_size - np.cumsum(principal_payments) return outstanding_balance
def calculate_remaining_balance(self): """Calculates the remaining balance""" # Determine the number of periods we are into the loan num_periods_elapsed = self.calculate_elapsed_periods() # Find out how much principal we have paid prin_paid_to_date = 0.0 for current_period in range(0, num_periods_elapsed): prin_paid_to_date -= round( npf.ppmt(self.annual_rate / 12, current_period, self.num_years * 12, self.principal_amt), 2) return self.principal_amt - prin_paid_to_date
def test_broadcast_decimal(self): # Use almost equal because precision is tested in the explicit tests, # this test is to ensure broadcast with Decimal is not broken. assert_almost_equal( npf.ipmt( Decimal('0.1') / Decimal('12'), list(range(5)), Decimal('24'), Decimal('2000')), [ Decimal('-17.29165168'), Decimal('-16.66666667'), Decimal('-16.03647345'), Decimal('-15.40102862'), Decimal('-14.76028842') ], 4) assert_almost_equal( npf.ppmt( Decimal('0.1') / Decimal('12'), list(range(5)), Decimal('24'), Decimal('2000')), [ Decimal('-74.998201'), Decimal('-75.62318601'), Decimal('-76.25337923'), Decimal('-76.88882405'), Decimal('-77.52956425') ], 4) assert_almost_equal( npf.ppmt( Decimal('0.1') / Decimal('12'), list(range(5)), Decimal('24'), Decimal('2000'), Decimal('0'), [Decimal('0'), Decimal('0'), Decimal('1'), 'end', 'begin']), [ Decimal('-74.998201'), Decimal('-75.62318601'), Decimal('-75.62318601'), Decimal('-76.88882405'), Decimal('-76.88882405') ], 4)
def test_ppmt_special_rate_decimal(self): # When rounded out to 8 decimal places like the float based test, # this should not equal the same value as the float, substituted # for the decimal def raise_error_because_not_equal(): assert_equal( round(npf.ppmt(Decimal('0.23') / Decimal('12'), 1, 60, Decimal('10000000000')), 8), Decimal('-90238044.232277036')) assert_raises(AssertionError, raise_error_because_not_equal) assert_equal(npf.ppmt(Decimal('0.23') / Decimal('12'), 1, 60, Decimal('10000000000')), Decimal('-90238044.2322778884413969909'))
def calculate_loan(self): """Compute annual payment of a loan. Inputs: quantity [monetary units] == investment which will be funded interest [as fraction of unity] == annual interest years == number of yeras to return the loan.""" assert type(self) is Loan self.res_payment = pmt(self.interest, self.years, self.quantity) self.res_interest = ipmt(self.interest, np.arange(self.years) + 1, self.years, self.quantity) self.res_principal = ppmt(self.interest, np.arange(self.years) + 1, self.years, self.quantity)
def loan(self, prestamo, interest, years): """Compute annual payment of a loan. Inputs: quantity [monetary units] == investment which will be funded interest [as fraction of unity] == annual interest years == number of yeras to return the loan.""" quantity = prestamo * self.capex() assert quantity > 0 assert interest >= 0 and interest <= 1 assert years > 1 loan_payment = pmt(interest, years, quantity) loan_interest = ipmt(interest, np.arange(years) + 1, years, quantity) loan_principal = ppmt(interest, np.arange(years) + 1, years, quantity) return loan_payment, loan_interest, loan_principal
def test_when(self): # begin assert_equal(npf.rate(10, 20, -3500, 10000, 1), npf.rate(10, 20, -3500, 10000, 'begin')) # end assert_equal(npf.rate(10, 20, -3500, 10000), npf.rate(10, 20, -3500, 10000, 'end')) assert_equal(npf.rate(10, 20, -3500, 10000, 0), npf.rate(10, 20, -3500, 10000, 'end')) # begin assert_equal(npf.pv(0.07, 20, 12000, 0, 1), npf.pv(0.07, 20, 12000, 0, 'begin')) # end assert_equal(npf.pv(0.07, 20, 12000, 0), npf.pv(0.07, 20, 12000, 0, 'end')) assert_equal(npf.pv(0.07, 20, 12000, 0, 0), npf.pv(0.07, 20, 12000, 0, 'end')) # begin assert_equal(npf.pmt(0.08 / 12, 5 * 12, 15000., 0, 1), npf.pmt(0.08 / 12, 5 * 12, 15000., 0, 'begin')) # end assert_equal(npf.pmt(0.08 / 12, 5 * 12, 15000., 0), npf.pmt(0.08 / 12, 5 * 12, 15000., 0, 'end')) assert_equal(npf.pmt(0.08 / 12, 5 * 12, 15000., 0, 0), npf.pmt(0.08 / 12, 5 * 12, 15000., 0, 'end')) # begin assert_equal(npf.ppmt(0.1 / 12, 1, 60, 55000, 0, 1), npf.ppmt(0.1 / 12, 1, 60, 55000, 0, 'begin')) # end assert_equal(npf.ppmt(0.1 / 12, 1, 60, 55000, 0), npf.ppmt(0.1 / 12, 1, 60, 55000, 0, 'end')) assert_equal(npf.ppmt(0.1 / 12, 1, 60, 55000, 0, 0), npf.ppmt(0.1 / 12, 1, 60, 55000, 0, 'end')) # begin assert_equal(npf.nper(0.075, -2000, 0, 100000., 1), npf.nper(0.075, -2000, 0, 100000., 'begin')) # end assert_equal(npf.nper(0.075, -2000, 0, 100000.), npf.nper(0.075, -2000, 0, 100000., 'end')) assert_equal(npf.nper(0.075, -2000, 0, 100000., 0), npf.nper(0.075, -2000, 0, 100000., 'end'))
def test_float(self): assert_allclose(npf.ppmt(0.1 / 12, 1, 60, 55000), -710.25, rtol=1e-4)
def loan_principal(self): assert self.quantity > 0 assert self.interest >= 0 and self.interest <= 1 assert self.years > 1 return ppmt(self.interest, np.arange(self.years) + 1, self.years, self.quantity)
def amortization_table(interest_rate, years, payments_year, principal, addl_principal=0, start_date=date.today()): """ Calculate the amortization schedule given the loan details Args: interest_rate: The annual interest rate for this loan years: Number of years for the loan payments_year: Number of payments in a year principal: Amount borrowed addl_principal (optional): Additional payments to be made each period. Assume 0 if nothing provided. must be a value less then 0, the function will convert a positive value to negative start_date (optional): Start date. Will start on first of next month if none provided Returns: schedule: Amortization schedule as a pandas dataframe summary: Pandas dataframe that summarizes the payoff information """ # Ensure the additional payments are negative if addl_principal > 0: addl_principal = -addl_principal # Create an index of the payment dates rng = pd.date_range(start_date, periods=years * payments_year, freq='MS') rng.name = "Payment_Date" # Build up the Amortization schedule as a DataFrame df = pd.DataFrame(index=rng, columns=[ 'Payment', 'Principal', 'Interest', 'Addl_Principal', 'Curr_Balance' ], dtype='float') # Add index by period (start at 1 not 0) df.reset_index(inplace=True) df.index += 1 df.index.name = "Period" # Calculate the payment, principal and interests amounts using built in Numpy functions per_payment = npf.pmt(interest_rate / payments_year, years * payments_year, principal) df["Payment"] = per_payment df["Principal"] = npf.ppmt(interest_rate / payments_year, df.index, years * payments_year, principal) df["Interest"] = npf.ipmt(interest_rate / payments_year, df.index, years * payments_year, principal) # Round the values df = df.round(2) # Add in the additional principal payments df["Addl_Principal"] = addl_principal # Store the Cumulative Principal Payments and ensure it never gets larger than the original principal df["Cumulative_Principal"] = (df["Principal"] + df["Addl_Principal"]).cumsum() df["Cumulative_Principal"] = df["Cumulative_Principal"].clip( lower=-principal) # Calculate the current balance for each period df["Curr_Balance"] = principal + df["Cumulative_Principal"] # Determine the last payment date try: last_payment = df.query("Curr_Balance <= 0")["Curr_Balance"].idxmax( axis=1, skipna=True) except ValueError: last_payment = df.last_valid_index() last_payment_date = "{:%m-%d-%Y}".format(df.loc[last_payment, "Payment_Date"]) # Truncate the data frame if we have additional principal payments: if addl_principal != 0: # Remove the extra payment periods df = df.loc[0:last_payment].copy() # Calculate the principal for the last row df.loc[last_payment, "Principal"] = -(df.loc[last_payment - 1, "Curr_Balance"]) # Calculate the total payment for the last row df.loc[last_payment, "Payment"] = df.loc[last_payment, ["Principal", "Interest"]].sum() # Zero out the additional principal df.loc[last_payment, "Addl_Principal"] = 0 # Get the payment info into a DataFrame in column order payment_info = (df[["Payment", "Principal", "Addl_Principal", "Interest"]].sum().to_frame().T) # Format the Date DataFrame payment_details = pd.DataFrame.from_dict( dict([('payoff_date', [last_payment_date]), ('Interest Rate', [interest_rate]), ('Number of years', [years])])) # Add a column showing how much we pay each period. # Combine addl principal with principal for total payment payment_details["Period_Payment"] = round(per_payment, 2) + addl_principal payment_summary = pd.concat([payment_details, payment_info], axis=1) return df, payment_summary #df, payment_summary = amortization_table(interest_rate, years, payments_year, principal) #print(payment_summary)
def test_broadcast_decimal(self, when, desired): args = (Decimal('0.1') / Decimal('12'), numpy.arange(1, 5), Decimal('24'), Decimal('2000'), Decimal('0')) result = npf.ppmt(*args) if when is None else npf.ppmt(*args, when) assert_almost_equal(result, desired, decimal=8)
f'年均收益率{rate * 100:.2f}%,每月投入¥{abs(float(pmt)):.2f}元,投资{nper}年后,期末资产¥{abs(float(fv)):.2f}元,需要投入本金:¥{abs(pv):.2f}元。' ) '''pmt:计算每期支付金额''' rate = 0.075 nper = 20 pv = 1000000 fv = 0 per = 240 pmt = npf.pmt(rate=rate / 12, nper=nper * 12, pv=pv, fv=fv) # rate:年化投资回报率 # nper:投资年数 # pv:现值(正值) # fv:预期达到的资产总值(正值) # per:还款的第几期 '''ppmt:每期支付金额之本金''' ppmt = npf.ppmt(rate=rate / 12, per=per, nper=nper * 12, pv=pv, fv=fv) '''ipmt:每期支付金额之利息''' ipmt = npf.ipmt(rate=rate / 12, per=per, nper=nper * 12, pv=pv, fv=fv) print( f'年利率{rate * 100}%,贷款总额¥{abs(pv)}元,{nper}年还清,每月还款¥{abs(pmt):.2f}元,第{per}期本金:¥{abs(ppmt):.2f}元,第{per}期利息:¥{abs(ipmt):.2f}元。' ) '''nper:分期数''' rate = 0.075 pmt = -8055.93 pv = 1000000 fv = 0 # rate:年利率 # pmt:每期还款金额(负值) # pv:贷款总额(正值) # fv:期末剩余贷款金额(正值) nper = npf.nper(rate=rate / 12, pmt=pmt, pv=pv, fv=fv)
def test_broadcast(self, when, desired): args = (0.1 / 12, numpy.arange(1, 5), 24, 2000, 0) result = npf.ppmt(*args) if when is None else npf.ppmt(*args, when) assert_allclose(result, desired, rtol=1e-5)
def test_invalid_per(self, args): # Note that math.isnan() handles Decimal NaN correctly. assert math.isnan(npf.ppmt(*args))
def test_decimal_with_when(self): """ Test that decimals are still supported if the when argument is passed """ # begin assert_equal( npf.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), Decimal('1')), npf.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), 'begin')) # end assert_equal( npf.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000')), npf.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), 'end')) assert_equal( npf.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), Decimal('0')), npf.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), 'end')) # begin assert_equal( npf.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), Decimal('1')), npf.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), 'begin')) # end assert_equal( npf.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0')), npf.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), 'end')) assert_equal( npf.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), Decimal('0')), npf.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), 'end')) # begin assert_equal( npf.pmt( Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), Decimal('0'), Decimal('1')), npf.pmt( Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), Decimal('0'), 'begin')) # end assert_equal( npf.pmt( Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), Decimal('0')), npf.pmt( Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), Decimal('0'), 'end')) assert_equal( npf.pmt( Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), Decimal('0'), Decimal('0')), npf.pmt( Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'), Decimal('0'), 'end')) # begin assert_equal( npf.ppmt( Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), Decimal('0'), Decimal('1')), npf.ppmt( Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), Decimal('0'), 'begin')) # end assert_equal( npf.ppmt( Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), Decimal('0')), npf.ppmt( Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), Decimal('0'), 'end')) assert_equal( npf.ppmt( Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), Decimal('0'), Decimal('0')), npf.ppmt( Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'), Decimal('0'), 'end'))
def test_ppmt(self): assert_equal(numpy.round(npf.ppmt(0.1 / 12, 1, 60, 55000), 2), -710.25)
def test_when_is_begin(self, when): assert_allclose( npf.ppmt(0.1 / 12, 1, 60, 55000, 0, when), -1158.929712, # Computed using Google Sheet's PPMT rtol=1e-9, )