Source code for financecalculator2025.n_periods

import math
import warnings

[docs] def n_periods(principal, annual_rate, future_value, contribution=0): """ Calculates the number of periods (in months) needed to reach a specified future value, given an initial principal, an annual interest rate, and optional monthly contributions. Parameters ---------- principal : float The initial amount, which can be a positive value for an investment or a negative value for a loan. annual_rate : float Annual interest rate (as a percentage, e.g., 5 for 5%). future_value : float The target future value of the investment or loan balance. contribution : float, optional Payment made per period (monthly contributions). Defaults to 0 if not provided. Returns ------- n_periods : int The number of periods (in months) required to reach the future value. Raises ------ TypeError If any of `principal`, `annual_rate`, `future_value`, or `contribution` is not a float or int. ValueError If both `principal` and `contribution` are 0. If `annual_rate` is negative. Warnings -------- UserWarning If `annual_rate` is unusually low (<1), indicating the user may have entered a percentage instead of a decimal. If `annual_rate` is 0, the future value cannot be reached without a contribution. If `n_periods` is unusually low (<5), suggesting the user may have entered years instead of months. Examples -------- >>> n_periods(principal=1000, annual_rate=5, future_value=2000, contribution=50) """ # check types of the inputs if not isinstance(principal, (int, float)): raise TypeError("Parameter 'principal' must be a number (int or float).") if not isinstance(annual_rate, (int, float)): raise TypeError("Parameter 'annual_rate' must be a number (int or float).") if not isinstance(future_value, (int, float)): raise TypeError("Parameter 'future_value' must be a number (int or float).") if not isinstance(contribution, (int, float)): raise TypeError("Parameter 'contribution' must be a number (int or float).") if principal == future_value: return 0 # convert annual rate to monthly rate monthly_rate = annual_rate / 100 / 12 # warning: user might input a decimal instead of a percentage if 0 < annual_rate < 1: warnings.warn("The annual_rate is unusually low. Did you mean to enter a percentage instead of a decimal?", UserWarning) # warning: annual rate <= 0 if annual_rate <= 0: warnings.warn("The annual_rate is zero or negative. This is unusual behavior.", UserWarning) # error raised when both principal and contribution are 0 if principal == 0 and contribution == 0: raise ValueError("Either principal or contribution must be non-zero to reach a future value.") # if monthly rate is 0, just simple calculation if monthly_rate == 0: if contribution == 0: raise ValueError("With a zero interest rate and no contribution, the future value cannot be reached.") n_periods = (future_value - principal) / contribution else: # compounding calculation n_periods = math.log((future_value * monthly_rate + contribution) / (principal * monthly_rate + contribution)) / math.log(1 + monthly_rate) # return a positive integar n_periods = max(1, round(n_periods)) return n_periods