API Reference¶
MoneyWarp provides a comprehensive set of classes for financial calculations and time-based analysis.
Core Classes¶
Money¶
money_warp.Money
¶
Represents a monetary amount with high internal precision.
Maintains full precision internally for calculations but provides 'real money' representation rounded to 2 decimal places for display and comparisons.
Attributes¶
raw_amount
property
¶
Get the high-precision internal amount.
real_amount
property
¶
Get the 'real money' amount rounded to 2 decimal places.
cents
property
¶
Get real amount in cents.
Functions¶
__init__(amount)
¶
Create a Money object with high internal precision.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
amount
|
Union[Decimal, str, int, float]
|
The monetary amount (will be converted to Decimal) |
required |
zero()
classmethod
¶
Create zero money.
from_cents(cents)
classmethod
¶
Create from cents to avoid decimal issues.
__add__(other)
¶
Add two Money objects.
__sub__(other)
¶
Subtract two Money objects.
__mul__(factor)
¶
Multiply by a number - keeps high precision.
__truediv__(divisor)
¶
Divide by a number - keeps high precision.
__radd__(other)
¶
Support numeric + Money (e.g. Decimal + Money).
__rsub__(other)
¶
Support numeric - Money (e.g. Decimal - Money).
__rmul__(factor)
¶
Support numeric * Money (e.g. float * Money).
__neg__()
¶
Negative money.
__abs__()
¶
Absolute value.
__float__()
¶
Float representation using full internal precision.
__eq__(other)
¶
Compare at 'real money' precision. Accepts Money or Decimal.
__lt__(other)
¶
Less than comparison at real money precision. Accepts Money or Decimal.
__le__(other)
¶
Less than or equal comparison at real money precision. Accepts Money or Decimal.
__gt__(other)
¶
Greater than comparison at real money precision. Accepts Money or Decimal.
__ge__(other)
¶
Greater than or equal comparison at real money precision. Accepts Money or Decimal.
to_real_money()
¶
Convert to real money (rounded to 2 decimal places).
is_positive()
¶
Check if amount is positive.
is_negative()
¶
Check if amount is negative.
is_zero()
¶
Check if amount is zero.
__str__()
¶
Display as real money (2 decimal places).
__repr__()
¶
Developer representation showing internal precision.
debug_precision()
¶
Show both internal and real amounts for debugging.
InterestRate¶
money_warp.InterestRate
¶
Bases: Rate
Represents a non-negative contractual interest rate.
Inherits all conversion and comparison behaviour from Rate, but rejects negative values at construction time. Use this type for loan terms, discount rates, and other contractual parameters.
For computed metrics that may be negative (IRR, MIRR), use Rate.
Functions¶
__init__(rate, period=None, as_percentage=False, precision=None, rounding=ROUND_HALF_UP, str_style='long', year_size=YearSize.commercial, str_decimals=3, abbrev_labels=None)
¶
Create a non-negative interest rate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rate
|
Union[str, Decimal, float]
|
Rate as string ("5.25% a", "0.004167 m") or numeric value. Abbreviated formats ("5.25% a.a.", "0.5% a.m.") are also accepted and automatically set str_style to "abbrev". Negative values are rejected. |
required |
period
|
Optional[CompoundingFrequency]
|
Compounding frequency (required if rate is numeric) |
None
|
as_percentage
|
bool
|
If True and rate is numeric, treat as percentage |
False
|
precision
|
Optional[int]
|
Number of decimal places for the effective annual rate during conversions. None keeps full precision. |
None
|
rounding
|
str
|
Rounding mode from the decimal module (e.g. ROUND_HALF_UP, ROUND_DOWN). Only used when precision is set. |
ROUND_HALF_UP
|
str_style
|
str
|
Controls period notation in str. "long" outputs the full name (e.g. "annually"), "abbrev" outputs the abbreviated form (e.g. "a.a."). |
'long'
|
year_size
|
YearSize
|
Day-count convention for daily conversions. YearSize.commercial (365) or YearSize.banker (360). |
commercial
|
str_decimals
|
int
|
Number of decimal places for the percentage in str. Default 3 gives "5.250%". |
3
|
abbrev_labels
|
Optional[Dict[CompoundingFrequency, str]]
|
Partial or full override of the default abbreviation map. Merged with _ABBREV_MAP so you only need to pass the keys you want to change. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the rate is negative. |
accrue(principal, days)
¶
Compute compound interest accrued on a principal over a number of days.
Formula: principal * ((1 + daily_rate) ** days - 1)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
principal
|
Money
|
The principal amount. |
required |
days
|
int
|
Number of days to accrue interest over. |
required |
Returns:
| Type | Description |
|---|---|
Money
|
The accrued interest (not including the principal). |
CashFlow¶
money_warp.CashFlow
¶
Container for a collection of cash flow items representing a financial stream.
Internally stores :class:CashFlowItem (temporal containers).
Public iteration and query methods resolve each item and filter out
deleted entries, so consumers see only active :class:CashFlowEntry
objects.
Attributes¶
query
property
¶
Create a query builder over active entries.
Functions¶
empty()
classmethod
¶
Create an empty cash flow.
add_item(item)
¶
Add a cash flow item to this stream.
add(amount, datetime, description=None, category=None, *, kind=CashFlowType.HAPPENED)
¶
Add a cash flow item by specifying its components.
items()
¶
Active entries at the current time (resolves and filters).
sorted_items()
¶
Active entries sorted by datetime.
raw_items()
¶
Underlying temporal containers (use for update/delete).
net_present_value()
¶
Simple sum of all active cash flows (no discounting).
total_inflows()
¶
Sum of all positive cash flows.
total_outflows()
¶
Sum of all negative cash flows (returned as positive amount).
filter_by_category(category)
¶
New CashFlow containing only items with the specified category.
filter_by_kind(kind)
¶
New CashFlow containing only items with the specified kind.
filter_by_datetime_range(start_datetime, end_datetime)
¶
New CashFlow containing only items within the datetime range.
CashFlowItem¶
money_warp.CashFlowItem
¶
Temporal container that wraps a timeline of :class:CashFlowEntry snapshots.
At any point in time exactly one entry (or None, meaning deleted)
is active. :meth:resolve returns that entry for
self.now().
The constructor accepts the same positional/keyword arguments as the
old CashFlowItem so that existing call-sites continue to work
without changes during the migration.
Loan¶
money_warp.Loan
¶
Represents a personal loan where everything emerges from the CashFlow.
The CashFlow is the single source of truth. Expected items (the amortization schedule) and actual payments both live in one CashFlow. Settlements, installment views, balances, and fines are all derived on demand -- nothing is decomposed or stored at payment time.
Payment allocation priority: Fine -> Mora Interest -> Interest -> Principal. Installment 1 is fully addressed before installment 2.
Examples:
>>> from money_warp import Loan, Money, InterestRate
>>> from datetime import date, datetime
>>>
>>> loan = Loan(
... Money("10000"),
... InterestRate("5% annual"),
... [date(2024, 2, 1), date(2024, 3, 1)]
... )
>>>
>>> loan.record_payment(Money("500"), datetime(2024, 2, 1))
>>> print(f"Balance: {loan.current_balance}")
Attributes¶
settlements
property
¶
All settlements (derived from CashFlow).
installments
property
¶
The repayment plan as Installment objects (derived from CashFlow).
principal_balance
property
¶
Outstanding principal (derived from CashFlow).
interest_balance
property
¶
Regular (non-mora) accrued interest since last payment.
mora_interest_balance
property
¶
Mora accrued interest since last payment.
fine_balance
property
¶
Unpaid fine amount (derived from CashFlow).
current_balance
property
¶
Total outstanding balance (principal + interest + mora + fines).
is_paid_off
property
¶
Whether the loan is fully paid off.
overpaid
property
¶
Total amount paid beyond the loan's obligations (derived from CashFlow).
fines_applied
property
writable
¶
Fine amounts applied per due date (derived from CashFlow).
total_fines
property
¶
Total amount of fines applied.
last_payment_date
property
¶
Date of the last payment, or disbursement date if none.
tax_amounts
property
¶
Per-tax results keyed by tax class name. Computed lazily.
total_tax
property
¶
Sum of all taxes applied to this loan.
net_disbursement
property
¶
Amount the borrower actually receives (principal minus total tax).
Functions¶
record_payment(amount, payment_date, interest_date=None, processing_date=None, description=None)
¶
Record a payment. Just one CashFlowItem -- everything else is derived.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
amount
|
Money
|
Total payment amount (positive value). |
required |
payment_date
|
datetime
|
When the money moved. |
required |
interest_date
|
Optional[datetime]
|
Cutoff date for interest accrual calculation. Defaults to payment_date. |
None
|
processing_date
|
Optional[datetime]
|
Unused, kept for API compatibility. |
None
|
description
|
Optional[str]
|
Optional description of the payment. |
None
|
Returns:
| Type | Description |
|---|---|
Settlement
|
Settlement describing how the payment was allocated. |
pay_installment(amount, description=None)
¶
Pay the next installment.
Interest accrual depends on timing: - Early/on-time: interest accrues up to the due date. - Late: interest accrues up to self.now().
When all installments are already paid the payment is recorded as an overpayment (no interest accrual) and a warning is issued.
After recording the payment, if the principal balance drifts from
the schedule's expected ending balance by a small amount (within
payment_tolerance), a tolerance adjustment CashFlowItem is
added to the cashflow to prevent rounding drift from compounding.
anticipate_payment(amount, installments=None, description=None)
¶
Make an early payment with interest discount.
Interest is calculated only up to self.now(), so the borrower pays less interest for fewer elapsed days.
When installments is provided (1-based), the corresponding expected cash-flow items are temporally deleted.
calculate_anticipation(installments)
¶
Calculate the amount to pay today to eliminate specific installments.
is_payment_late(due_date, as_of_date=None)
¶
Check if a payment is late considering the grace period.
calculate_late_fines(as_of_date=None)
¶
Compute and record fine observations as of a date.
Appends the observation date so that subsequent property queries include fines for due dates overdue at that point.
Returns the amount of NEW fines applied (zero if already applied).
now()
¶
Current datetime (Warp-aware via shared TimeContext).
days_since_last_payment()
¶
Days since the last payment (Warp-aware).
get_expected_payment_amount(due_date)
¶
Get the expected payment amount for a specific due date.
get_original_schedule()
¶
The original amortization schedule (static, ignores payments).
get_amortization_schedule()
¶
Current schedule: recorded past entries + projected future.
generate_expected_cash_flow()
¶
Expected cash flow (schedule items only, no payments).
present_value(discount_rate=None, valuation_date=None)
¶
Present Value of the loan's expected cash flows.
irr(guess=None)
¶
Internal Rate of Return of the loan's expected cash flows.
Time Machine¶
Warp¶
money_warp.Warp
¶
Time Machine context manager for financial projections and analysis.
Allows you to temporarily "warp" any financial instrument (Loan,
CreditCard, or any object with a _time_ctx attribute) to a
specific date to analyze its state at that point in time.
Usage
instrument = Loan(...) # or CreditCard(...) with Warp(instrument, '2030-01-15') as warped: balance = warped.current_balance
Warping different objects concurrently is allowed, but nested warps on the same object are not.
The target object must expose _time_ctx (a :class:TimeContext).
If it also has an _on_warp(target_date) method, that method is
called after overriding the time context on the clone.
Attributes¶
original_loan
property
¶
Backward-compatible alias for the original target.
warped_loan
property
writable
¶
Backward-compatible alias for the warped clone.
Functions¶
__init__(target, target_date)
¶
Initialize the Warp context manager.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
Any
|
The financial instrument to warp (Loan, CreditCard, etc.).
Must have a |
required |
target_date
|
Union[str, date, datetime]
|
The date to warp to (accepts strings, date, or datetime objects) |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
If target has no |
NestedWarpError
|
If the same object is already being warped |
InvalidDateError
|
If the target_date cannot be parsed |
__enter__()
¶
Enter the Warp context and return a time-warped clone.
Returns:
| Type | Description |
|---|---|
Any
|
A deep-cloned object with its TimeContext overridden to the |
Any
|
target date. |
__exit__(exc_type, exc_val, exc_tb)
¶
Exit the Warp context and clean up.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exc_type
|
Optional[Type[BaseException]]
|
Exception type (if any) |
required |
exc_val
|
Optional[BaseException]
|
Exception value (if any) |
required |
exc_tb
|
Optional[object]
|
Exception traceback (if any) |
required |
__str__()
¶
String representation of the Warp.
__repr__()
¶
Detailed representation for debugging.
WarpedTime¶
money_warp.warp.WarpedTime
¶
Exceptions¶
WarpError¶
money_warp.WarpError
¶
Bases: Exception
Base exception for Warp-related errors.
NestedWarpError¶
money_warp.NestedWarpError
¶
Bases: WarpError
Raised when attempting to create nested Warp contexts.
InvalidDateError¶
Schedulers¶
PriceScheduler¶
money_warp.PriceScheduler
¶
Bases: BaseScheduler
Price scheduler implementing Progressive Price Schedule (French amortization system).
This scheduler calculates a fixed payment amount (PMT) and then allocates each payment between interest and principal. Interest is calculated on the outstanding balance, and the remainder goes to principal reduction.
Based on the reference implementation from cartaorobbin/loan-calculator.
Functions¶
__init__(principal=None, daily_interest_rate=None, return_days=None, disbursement_date=None)
¶
Initialize the scheduler with loan parameters.
generate_schedule(principal, interest_rate, due_dates, disbursement_date, tz)
classmethod
¶
Generate Progressive Price Schedule with fixed payment amounts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
principal
|
Money
|
The loan amount |
required |
interest_rate
|
InterestRate
|
The annual interest rate |
required |
due_dates
|
List[date]
|
List of payment due dates |
required |
disbursement_date
|
datetime
|
When the loan was disbursed |
required |
tz
|
tzinfo
|
Business timezone for extracting calendar dates from datetimes |
required |
Returns:
| Type | Description |
|---|---|
PaymentSchedule
|
PaymentSchedule with fixed payment amounts and interest/principal allocation |
calculate_constant_return_pmt()
¶
Calculate PMT using the reference formula from loan-calculator.
PMT = principal / sum(1 / (1 + daily_rate)^n for n in return_days)
Returns:
| Type | Description |
|---|---|
Decimal
|
The fixed payment amount (PMT) |
InvertedPriceScheduler¶
money_warp.InvertedPriceScheduler
¶
Bases: BaseScheduler
Inverted Price scheduler implementing Constant Amortization System (SAC).
This scheduler calculates a fixed principal payment amount for each period and adds variable interest calculated on the outstanding balance. This results in decreasing total payments over time.
Key characteristics: - Fixed principal payment each period - Variable interest payment (decreases over time) - Variable total payment (decreases over time) - Faster debt reduction compared to Price scheduler
Functions¶
generate_schedule(principal, interest_rate, due_dates, disbursement_date, tz)
classmethod
¶
Generate Constant Amortization Schedule with fixed principal payments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
principal
|
Money
|
The loan amount |
required |
interest_rate
|
InterestRate
|
The annual interest rate |
required |
due_dates
|
List[date]
|
List of payment due dates |
required |
disbursement_date
|
datetime
|
When the loan was disbursed |
required |
tz
|
tzinfo
|
Business timezone for extracting calendar dates from datetimes |
required |
Returns:
| Type | Description |
|---|---|
PaymentSchedule
|
PaymentSchedule with fixed principal amounts and variable total payments |
BaseScheduler¶
money_warp.BaseScheduler
¶
Bases: ABC
Abstract base class for all payment schedulers.
All schedulers should inherit from this and implement the generate_schedule class method.
Functions¶
generate_schedule(principal, interest_rate, due_dates, disbursement_date, tz)
abstractmethod
classmethod
¶
Generate the payment schedule.
This is the only public method that schedulers need to implement.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
principal
|
Money
|
The loan amount |
required |
interest_rate
|
InterestRate
|
The annual interest rate |
required |
due_dates
|
List[date]
|
List of payment due dates |
required |
disbursement_date
|
datetime
|
When the loan was disbursed |
required |
tz
|
tzinfo
|
Business timezone for extracting calendar dates from datetimes |
required |
Returns:
| Type | Description |
|---|---|
PaymentSchedule
|
PaymentSchedule with all payment details |
Installment & Settlement¶
Installment¶
money_warp.Installment
dataclass
¶
A single installment in a loan repayment plan.
Represents the borrower's obligation for one period: what is expected, what has actually been paid, and the detailed per-payment allocations.
Installments are derived views -- the Loan builds them on demand from the CashFlow and the schedule.
Attributes¶
balance
property
¶
The amount still owed to fully settle this installment.
is_fully_paid
property
¶
Whether this installment has been fully settled.
Functions¶
from_schedule_entry(entry, allocations, expected_mora, expected_fine)
classmethod
¶
Build an Installment from a scheduler's PaymentScheduleEntry.
Settlement¶
money_warp.Settlement
dataclass
¶
Result of applying a payment to a loan.
Captures the full allocation of a single payment across fines, interest, mora interest, and principal, along with per-installment detail showing which installments were covered.
Allocation¶
money_warp.Allocation
dataclass
¶
Data Classes¶
PaymentSchedule¶
money_warp.PaymentSchedule
dataclass
¶
Complete payment schedule for a loan.
Contains all payment entries and summary information.
PaymentScheduleEntry¶
money_warp.PaymentScheduleEntry
dataclass
¶
Query Interface¶
CashFlowQuery¶
money_warp.CashFlowQuery
¶
SQLAlchemy-style query builder for filtering cash flow entries.
Works with both :class:CashFlowEntry and :class:CashFlowItem
objects — both expose the same attribute interface (amount,
datetime, description, category).
Attributes¶
expected
property
¶
Shortcut for filter_by(kind=CashFlowType.EXPECTED).
happened
property
¶
Shortcut for filter_by(kind=CashFlowType.HAPPENED).
Functions¶
filter_by(predicate=None, **kwargs)
¶
Filter items using keyword arguments or a predicate function.
Keyword arguments: - kind: Filter by CashFlowType (EXPECTED or HAPPENED) - category: Filter by category - amount / amount__gt / amount__gte / amount__lt / amount__lte - datetime / datetime__gt / datetime__gte / datetime__lt / datetime__lte - description: Filter by description - is_inflow: Filter to only inflows (True) or outflows (False)
order_by(*fields)
¶
Order items by one or more fields.
Prefix with '-' for descending: '-datetime', '-amount'.
to_cash_flow()
¶
Convert query result back to a CashFlow.
If the query holds :class:CashFlowEntry objects they are
wrapped in fresh :class:CashFlowItem containers.
Enums¶
CompoundingFrequency¶
money_warp.CompoundingFrequency
¶
Bases: Enum
Frequency of compounding per year.
YearSize¶
money_warp.YearSize
¶
Bases: Enum
Number of days that constitute one year for rate conversions.
Present Value Functions¶
present_value¶
money_warp.present_value
¶
Present Value and IRR calculations for cash flows and financial instruments.
Classes¶
Functions¶
present_value(cash_flow, discount_rate, valuation_date=None)
¶
Calculate the Present Value (PV) of a cash flow stream.
The Present Value is the sum of all future cash flows discounted back to the valuation date using the specified discount rate.
Formula: PV = Σ(CF_t / (1 + r)^t) where: - CF_t = Cash flow at time t - r = Discount rate per period - t = Time periods from valuation date
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cash_flow
|
CashFlow
|
The cash flow stream to evaluate |
required |
discount_rate
|
Rate
|
The discount rate to use for discounting |
required |
valuation_date
|
Optional[datetime]
|
Date to discount back to (defaults to earliest cash flow date) |
None
|
Returns:
| Type | Description |
|---|---|
Money
|
The present value of the cash flow stream |
Examples:
>>> from datetime import datetime
>>> from money_warp import CashFlow, CashFlowItem, Money, InterestRate, present_value
>>>
>>> # Create a simple cash flow
>>> items = [
... CashFlowItem(Money("1000"), datetime(2024, 1, 1), "Initial investment", "investment"),
... CashFlowItem(Money("-1100"), datetime(2024, 12, 31), "Return", "return"),
... ]
>>> cf = CashFlow(items)
>>>
>>> # Calculate PV with 5% discount rate
>>> pv = present_value(cf, InterestRate("5% annual"))
>>> print(f"Present Value: {pv}") # Should be close to zero for 10% return vs 5% discount
present_value_of_annuity(payment_amount, interest_rate, periods, payment_timing='end')
¶
Calculate the Present Value of an ordinary annuity or annuity due.
An annuity is a series of equal payments made at regular intervals.
Formula for ordinary annuity (payments at end of period): PV = PMT x [(1 - (1 + r)^(-n)) / r]
Formula for annuity due (payments at beginning of period): PV = PMT x [(1 - (1 + r)^(-n)) / r] x (1 + r)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payment_amount
|
Money
|
The amount of each payment |
required |
interest_rate
|
InterestRate
|
The interest rate per period |
required |
periods
|
int
|
The number of payment periods |
required |
payment_timing
|
str
|
"end" for ordinary annuity, "begin" for annuity due |
'end'
|
Returns:
| Type | Description |
|---|---|
Money
|
The present value of the annuity |
Examples:
present_value_of_perpetuity(payment_amount, interest_rate)
¶
Calculate the Present Value of a perpetuity.
A perpetuity is a series of equal payments that continue forever.
Formula: PV = PMT / r
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payment_amount
|
Money
|
The amount of each payment |
required |
interest_rate
|
InterestRate
|
The interest rate per period |
required |
Returns:
| Type | Description |
|---|---|
Money
|
The present value of the perpetuity |
Raises:
| Type | Description |
|---|---|
ValueError
|
If interest rate is zero or negative |
Examples:
discount_factor(interest_rate, periods)
¶
Calculate the discount factor for a given interest rate and time periods.
Formula: DF = 1 / (1 + r)^n
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
interest_rate
|
Rate
|
The interest rate per period |
required |
periods
|
Union[int, Decimal]
|
The number of periods (can be fractional) |
required |
Returns:
| Type | Description |
|---|---|
Decimal
|
The discount factor as a Decimal |
Examples:
internal_rate_of_return(cash_flow, guess=None, year_size=YearSize.commercial)
¶
Calculate the Internal Rate of Return (IRR) of a cash flow stream.
The IRR is the discount rate that makes the Net Present Value (NPV) equal to zero. It represents the effective annual rate of return of the investment.
Uses scipy.optimize.fsolve for robust numerical solution.
Note: To calculate IRR from a specific date, use the Time Machine: with Warp(loan, target_date) as warped_loan: irr = warped_loan.irr()
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cash_flow
|
CashFlow
|
The cash flow stream to analyze |
required |
guess
|
Optional[Rate]
|
Initial guess for IRR (defaults to 10% annual) |
None
|
year_size
|
YearSize
|
Day-count convention (YearSize.commercial for 365 days, YearSize.banker for 360 days) |
commercial
|
Returns:
| Type | Description |
|---|---|
Rate
|
The internal rate of return as a Rate (may be negative) |
Raises:
| Type | Description |
|---|---|
ValueError
|
If IRR cannot be found (no solution or doesn't converge) |
Examples:
>>> from datetime import datetime
>>> from money_warp import CashFlow, CashFlowItem, Money, internal_rate_of_return
>>>
>>> # Simple investment: -$1000 now, +$1100 in 1 year
>>> items = [
... CashFlowItem(Money("-1000"), datetime(2024, 1, 1), "Investment", "investment"),
... CashFlowItem(Money("1100"), datetime(2024, 12, 31), "Return", "return"),
... ]
>>> cf = CashFlow(items)
>>>
>>> irr = internal_rate_of_return(cf)
>>> print(f"IRR: {irr}") # Should be approximately 10%
irr(cash_flow, guess=None, year_size=YearSize.commercial)
¶
Calculate the Internal Rate of Return (IRR) of a cash flow stream.
This is a convenience function that calls internal_rate_of_return() with default parameters for most common use cases.
Note: To calculate IRR from a specific date, use the Time Machine: with Warp(loan, target_date) as warped_loan: irr = warped_loan.irr()
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cash_flow
|
CashFlow
|
The cash flow stream to analyze |
required |
guess
|
Optional[Rate]
|
Initial guess for IRR (defaults to 10% annual) |
None
|
year_size
|
YearSize
|
Day-count convention (YearSize.commercial for 365 days, YearSize.banker for 360 days) |
commercial
|
Returns:
| Type | Description |
|---|---|
Rate
|
The internal rate of return as a Rate (may be negative) |
Examples:
>>> from datetime import datetime
>>> from money_warp import CashFlow, CashFlowItem, Money, irr
>>>
>>> # Investment analysis
>>> items = [
... CashFlowItem(Money("-5000"), datetime(2024, 1, 1), "Initial investment", "investment"),
... CashFlowItem(Money("1500"), datetime(2024, 6, 1), "Return 1", "return"),
... CashFlowItem(Money("2000"), datetime(2024, 12, 1), "Return 2", "return"),
... CashFlowItem(Money("2500"), datetime(2025, 6, 1), "Final return", "return"),
... ]
>>> cf = CashFlow(items)
>>>
>>> investment_irr = irr(cf)
>>> print(f"Investment IRR: {investment_irr}")
modified_internal_rate_of_return(cash_flow, finance_rate, reinvestment_rate, year_size=YearSize.commercial)
¶
Calculate the Modified Internal Rate of Return (MIRR).
MIRR addresses some limitations of IRR by using different rates for financing negative cash flows and reinvesting positive cash flows.
Formula: MIRR = (FV of positive flows / PV of negative flows)^(1/n) - 1
Note: To calculate MIRR from a specific date, use the Time Machine: with Warp(loan, target_date) as warped_loan: mirr = modified_internal_rate_of_return(warped_loan.generate_expected_cash_flow(), ...)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cash_flow
|
CashFlow
|
The cash flow stream to analyze |
required |
finance_rate
|
InterestRate
|
Rate for financing negative cash flows |
required |
reinvestment_rate
|
InterestRate
|
Rate for reinvesting positive cash flows |
required |
year_size
|
YearSize
|
Day-count convention (YearSize.commercial for 365 days, YearSize.banker for 360 days) |
commercial
|
Returns:
| Type | Description |
|---|---|
Rate
|
The modified internal rate of return as a Rate (may be negative) |
Examples:
>>> from datetime import datetime
>>> from money_warp import CashFlow, CashFlowItem, Money, InterestRate, modified_internal_rate_of_return
>>>
>>> # Investment with different financing and reinvestment rates
>>> items = [
... CashFlowItem(Money("-1000"), datetime(2024, 1, 1), "Investment", "investment"),
... CashFlowItem(Money("300"), datetime(2024, 6, 1), "Return 1", "return"),
... CashFlowItem(Money("400"), datetime(2024, 12, 1), "Return 2", "return"),
... CashFlowItem(Money("500"), datetime(2025, 6, 1), "Return 3", "return"),
... ]
>>> cf = CashFlow(items)
>>>
>>> mirr = modified_internal_rate_of_return(
... cf,
... InterestRate("8% annual"), # Financing rate
... InterestRate("6% annual") # Reinvestment rate
... )
>>> print(f"MIRR: {mirr}")
present_value_of_annuity¶
money_warp.present_value_of_annuity(payment_amount, interest_rate, periods, payment_timing='end')
¶
Calculate the Present Value of an ordinary annuity or annuity due.
An annuity is a series of equal payments made at regular intervals.
Formula for ordinary annuity (payments at end of period): PV = PMT x [(1 - (1 + r)^(-n)) / r]
Formula for annuity due (payments at beginning of period): PV = PMT x [(1 - (1 + r)^(-n)) / r] x (1 + r)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payment_amount
|
Money
|
The amount of each payment |
required |
interest_rate
|
InterestRate
|
The interest rate per period |
required |
periods
|
int
|
The number of payment periods |
required |
payment_timing
|
str
|
"end" for ordinary annuity, "begin" for annuity due |
'end'
|
Returns:
| Type | Description |
|---|---|
Money
|
The present value of the annuity |
Examples:
present_value_of_perpetuity¶
money_warp.present_value_of_perpetuity(payment_amount, interest_rate)
¶
Calculate the Present Value of a perpetuity.
A perpetuity is a series of equal payments that continue forever.
Formula: PV = PMT / r
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payment_amount
|
Money
|
The amount of each payment |
required |
interest_rate
|
InterestRate
|
The interest rate per period |
required |
Returns:
| Type | Description |
|---|---|
Money
|
The present value of the perpetuity |
Raises:
| Type | Description |
|---|---|
ValueError
|
If interest rate is zero or negative |
Examples:
discount_factor¶
money_warp.discount_factor(interest_rate, periods)
¶
Calculate the discount factor for a given interest rate and time periods.
Formula: DF = 1 / (1 + r)^n
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
interest_rate
|
Rate
|
The interest rate per period |
required |
periods
|
Union[int, Decimal]
|
The number of periods (can be fractional) |
required |
Returns:
| Type | Description |
|---|---|
Decimal
|
The discount factor as a Decimal |
Examples:
IRR Functions¶
internal_rate_of_return¶
money_warp.internal_rate_of_return(cash_flow, guess=None, year_size=YearSize.commercial)
¶
Calculate the Internal Rate of Return (IRR) of a cash flow stream.
The IRR is the discount rate that makes the Net Present Value (NPV) equal to zero. It represents the effective annual rate of return of the investment.
Uses scipy.optimize.fsolve for robust numerical solution.
Note: To calculate IRR from a specific date, use the Time Machine: with Warp(loan, target_date) as warped_loan: irr = warped_loan.irr()
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cash_flow
|
CashFlow
|
The cash flow stream to analyze |
required |
guess
|
Optional[Rate]
|
Initial guess for IRR (defaults to 10% annual) |
None
|
year_size
|
YearSize
|
Day-count convention (YearSize.commercial for 365 days, YearSize.banker for 360 days) |
commercial
|
Returns:
| Type | Description |
|---|---|
Rate
|
The internal rate of return as a Rate (may be negative) |
Raises:
| Type | Description |
|---|---|
ValueError
|
If IRR cannot be found (no solution or doesn't converge) |
Examples:
>>> from datetime import datetime
>>> from money_warp import CashFlow, CashFlowItem, Money, internal_rate_of_return
>>>
>>> # Simple investment: -$1000 now, +$1100 in 1 year
>>> items = [
... CashFlowItem(Money("-1000"), datetime(2024, 1, 1), "Investment", "investment"),
... CashFlowItem(Money("1100"), datetime(2024, 12, 31), "Return", "return"),
... ]
>>> cf = CashFlow(items)
>>>
>>> irr = internal_rate_of_return(cf)
>>> print(f"IRR: {irr}") # Should be approximately 10%
irr¶
money_warp.irr(cash_flow, guess=None, year_size=YearSize.commercial)
¶
Calculate the Internal Rate of Return (IRR) of a cash flow stream.
This is a convenience function that calls internal_rate_of_return() with default parameters for most common use cases.
Note: To calculate IRR from a specific date, use the Time Machine: with Warp(loan, target_date) as warped_loan: irr = warped_loan.irr()
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cash_flow
|
CashFlow
|
The cash flow stream to analyze |
required |
guess
|
Optional[Rate]
|
Initial guess for IRR (defaults to 10% annual) |
None
|
year_size
|
YearSize
|
Day-count convention (YearSize.commercial for 365 days, YearSize.banker for 360 days) |
commercial
|
Returns:
| Type | Description |
|---|---|
Rate
|
The internal rate of return as a Rate (may be negative) |
Examples:
>>> from datetime import datetime
>>> from money_warp import CashFlow, CashFlowItem, Money, irr
>>>
>>> # Investment analysis
>>> items = [
... CashFlowItem(Money("-5000"), datetime(2024, 1, 1), "Initial investment", "investment"),
... CashFlowItem(Money("1500"), datetime(2024, 6, 1), "Return 1", "return"),
... CashFlowItem(Money("2000"), datetime(2024, 12, 1), "Return 2", "return"),
... CashFlowItem(Money("2500"), datetime(2025, 6, 1), "Final return", "return"),
... ]
>>> cf = CashFlow(items)
>>>
>>> investment_irr = irr(cf)
>>> print(f"Investment IRR: {investment_irr}")
modified_internal_rate_of_return¶
money_warp.modified_internal_rate_of_return(cash_flow, finance_rate, reinvestment_rate, year_size=YearSize.commercial)
¶
Calculate the Modified Internal Rate of Return (MIRR).
MIRR addresses some limitations of IRR by using different rates for financing negative cash flows and reinvesting positive cash flows.
Formula: MIRR = (FV of positive flows / PV of negative flows)^(1/n) - 1
Note: To calculate MIRR from a specific date, use the Time Machine: with Warp(loan, target_date) as warped_loan: mirr = modified_internal_rate_of_return(warped_loan.generate_expected_cash_flow(), ...)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cash_flow
|
CashFlow
|
The cash flow stream to analyze |
required |
finance_rate
|
InterestRate
|
Rate for financing negative cash flows |
required |
reinvestment_rate
|
InterestRate
|
Rate for reinvesting positive cash flows |
required |
year_size
|
YearSize
|
Day-count convention (YearSize.commercial for 365 days, YearSize.banker for 360 days) |
commercial
|
Returns:
| Type | Description |
|---|---|
Rate
|
The modified internal rate of return as a Rate (may be negative) |
Examples:
>>> from datetime import datetime
>>> from money_warp import CashFlow, CashFlowItem, Money, InterestRate, modified_internal_rate_of_return
>>>
>>> # Investment with different financing and reinvestment rates
>>> items = [
... CashFlowItem(Money("-1000"), datetime(2024, 1, 1), "Investment", "investment"),
... CashFlowItem(Money("300"), datetime(2024, 6, 1), "Return 1", "return"),
... CashFlowItem(Money("400"), datetime(2024, 12, 1), "Return 2", "return"),
... CashFlowItem(Money("500"), datetime(2025, 6, 1), "Return 3", "return"),
... ]
>>> cf = CashFlow(items)
>>>
>>> mirr = modified_internal_rate_of_return(
... cf,
... InterestRate("8% annual"), # Financing rate
... InterestRate("6% annual") # Reinvestment rate
... )
>>> print(f"MIRR: {mirr}")
Date Generation Utilities¶
generate_monthly_dates¶
money_warp.generate_monthly_dates(start_date, num_payments)
¶
Generate a list of monthly payment due dates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start_date
|
datetime
|
The starting date (first payment date) |
required |
num_payments
|
int
|
Number of monthly payments to generate |
required |
Returns:
| Type | Description |
|---|---|
List[datetime]
|
List of datetime objects representing monthly due dates |
Examples:
generate_biweekly_dates¶
money_warp.generate_biweekly_dates(start_date, num_payments)
¶
Generate a list of bi-weekly (every 14 days) payment due dates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start_date
|
datetime
|
The starting date (first payment date) |
required |
num_payments
|
int
|
Number of bi-weekly payments to generate |
required |
Returns:
| Type | Description |
|---|---|
List[datetime]
|
List of datetime objects representing bi-weekly due dates |
Examples:
generate_weekly_dates¶
money_warp.generate_weekly_dates(start_date, num_payments)
¶
Generate a list of weekly payment due dates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start_date
|
datetime
|
The starting date (first payment date) |
required |
num_payments
|
int
|
Number of weekly payments to generate |
required |
Returns:
| Type | Description |
|---|---|
List[datetime]
|
List of datetime objects representing weekly due dates |
Examples:
generate_quarterly_dates¶
money_warp.generate_quarterly_dates(start_date, num_payments)
¶
Generate a list of quarterly payment due dates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start_date
|
datetime
|
The starting date (first payment date) |
required |
num_payments
|
int
|
Number of quarterly payments to generate |
required |
Returns:
| Type | Description |
|---|---|
List[datetime]
|
List of datetime objects representing quarterly due dates |
Examples:
generate_annual_dates¶
money_warp.generate_annual_dates(start_date, num_payments)
¶
Generate a list of annual payment due dates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start_date
|
datetime
|
The starting date (first payment date) |
required |
num_payments
|
int
|
Number of annual payments to generate |
required |
Returns:
| Type | Description |
|---|---|
List[datetime]
|
List of datetime objects representing annual due dates |
Examples:
generate_custom_interval_dates¶
money_warp.generate_custom_interval_dates(start_date, num_payments, interval_days)
¶
Generate a list of payment due dates with custom day intervals.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start_date
|
datetime
|
The starting date (first payment date) |
required |
num_payments
|
int
|
Number of payments to generate |
required |
interval_days
|
int
|
Number of days between payments |
required |
Returns:
| Type | Description |
|---|---|
List[datetime]
|
List of datetime objects representing due dates |
Examples:
Tax Classes¶
BaseTax¶
money_warp.BaseTax
¶
Bases: ABC
Abstract base class for all loan taxes.
All taxes should inherit from this and implement the calculate method. The interface mirrors BaseScheduler: simple, one method, receives what it needs.
Functions¶
calculate(schedule, disbursement_date, tz)
abstractmethod
¶
Calculate tax based on the amortization schedule.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
schedule
|
PaymentSchedule
|
The loan's payment schedule with principal breakdown per installment. |
required |
disbursement_date
|
datetime
|
When the loan was disbursed. |
required |
tz
|
tzinfo
|
Business timezone for extracting calendar dates from datetimes. |
required |
Returns:
| Type | Description |
|---|---|
TaxResult
|
TaxResult with total tax and per-installment breakdown. |
IOF¶
money_warp.IOF
¶
Bases: BaseTax
Brazilian IOF tax on loan operations.
IOF has two components applied to each installment's principal payment: - Daily rate: applied per day from disbursement to payment date (capped at max_daily_days) - Additional rate: flat percentage applied once per installment
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
daily_rate
|
Union[str, Decimal]
|
Daily IOF rate as decimal or string (e.g., Decimal("0.000082") or "0.0082%") |
required |
additional_rate
|
Union[str, Decimal]
|
Additional flat IOF rate as decimal or string (e.g., Decimal("0.0038") or "0.38%") |
required |
max_daily_days
|
int
|
Maximum number of days for daily rate calculation (default 365) |
365
|
rounding
|
IOFRounding
|
Rounding strategy for component aggregation (default PRECISE) |
PRECISE
|
Attributes¶
daily_rate
property
¶
The daily IOF rate as a decimal.
additional_rate
property
¶
The additional flat IOF rate as a decimal.
max_daily_days
property
¶
Maximum days for daily rate calculation.
rounding
property
¶
The rounding strategy used for component aggregation.
Functions¶
calculate(schedule, disbursement_date, tz)
¶
Calculate IOF for each installment in the schedule.
For each installment
days = min(days_from_disbursement_to_due_date, max_daily_days) daily_iof = principal_payment * daily_rate * days additional_iof = principal_payment * additional_rate installment_tax = daily_iof + additional_iof
IndividualIOF¶
money_warp.IndividualIOF
¶
Bases: IOF
IOF for Pessoa Fisica (PF) -- individual/natural person borrowers.
Pre-configured with the standard PF rates: - Daily rate: 0.0082% (0.000082) - Additional rate: 0.38% (0.0038)
All parameters can be overridden if the rates change by regulation.
CorporateIOF¶
money_warp.CorporateIOF
¶
Bases: IOF
IOF for Pessoa Juridica (PJ) -- legal entity/company borrowers.
Pre-configured with the standard PJ rates: - Daily rate: 0.0041% (0.000041) - Additional rate: 0.38% (0.0038)
All parameters can be overridden if the rates change by regulation.
IOFRounding¶
money_warp.IOFRounding
¶
Bases: Enum
Rounding strategy for IOF component aggregation.
sum high-precision daily and additional components, round once
per installment. This is the mathematically purer approach.
PER_COMPONENT: round each component (daily, additional) to 2 decimal places before summing. Matches the behavior of common Brazilian lending platforms.
TaxResult¶
money_warp.TaxResult
dataclass
¶
Result of a tax calculation across an entire schedule.
TaxInstallmentDetail¶
money_warp.TaxInstallmentDetail
dataclass
¶
Tax breakdown for a single installment.
Timezone Functions¶
get_tz¶
money_warp.get_tz()
¶
Return the current default timezone.
set_tz¶
money_warp.set_tz(tz)
¶
Set the default timezone.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tz
|
Union[str, tzinfo]
|
A timezone name (e.g. |
required |
now¶
money_warp.now()
¶
Return the current time in the configured timezone (always aware).
ensure_aware¶
money_warp.ensure_aware(dt)
¶
Guarantee that dt is timezone-aware and normalised to UTC.
If dt is naive, it is interpreted as being in the configured
business timezone (_default_tz) and then converted to UTC.
If dt is already aware, it is converted to UTC directly.
All datetimes stored inside the library are UTC. Use
:func:to_date when extracting a calendar date — it converts
back to the business timezone first.
tz_aware¶
money_warp.tz_aware(func)
¶
Decorator that makes every datetime argument timezone-aware.
At call time each positional and keyword argument is inspected:
datetimevalues are passed through :func:ensure_aware.listvalues whose first element is adatetimeare coerced element-wise.- Everything else is left untouched.
Grossup Functions¶
grossup¶
money_warp.grossup
¶
Grossup calculation using scipy.optimize.brentq bracketed root-finding.
Classes¶
GrossupResult
¶
Result of a grossup calculation.
Carries the grossed-up principal, the original requested amount, the
computed tax, and all the parameters needed to construct a Loan via
the convenience method to_loan().
Attributes:
| Name | Type | Description |
|---|---|---|
principal |
The grossed-up principal (loan amount including financed tax). |
|
requested_amount |
The amount the borrower actually receives. |
|
total_tax |
Total tax computed on the grossed-up principal. |
to_loan(**loan_kwargs)
¶Create a Loan from this grossup result.
All schedule parameters (principal, interest_rate, due_dates,
disbursement_date, scheduler, taxes) are forwarded automatically.
Pass any additional Loan keyword arguments (fine_rate,
grace_period_days, mora_interest_rate, mora_strategy) via
loan_kwargs.
Returns:
| Type | Description |
|---|---|
Loan
|
A fully configured Loan with the grossed-up principal. |
Functions¶
grossup(requested_amount, interest_rate, due_dates, disbursement_date, scheduler, taxes, tz)
¶
Compute the grossed-up principal so that principal - total_tax = requested_amount.
The borrower wants to receive requested_amount. Taxes are calculated on
the loan principal, which must be larger to compensate. Uses
scipy.optimize.brentq (bracketed bisection) to find the root of
f(p) = p - requested_amount - tax(p) = 0.
brentq is preferred over fsolve because the objective function has
a staircase shape (cent-level rounding in schedule/tax computation makes it
non-smooth), which can cause fsolve's numerical Jacobian to stall.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
requested_amount
|
Money
|
The net amount the borrower wants to receive. |
required |
interest_rate
|
InterestRate
|
The loan interest rate. |
required |
due_dates
|
list[date]
|
Payment due dates. |
required |
disbursement_date
|
datetime
|
When the loan is disbursed. |
required |
scheduler
|
type[BaseScheduler]
|
Scheduler class for generating the amortization schedule. |
required |
taxes
|
list[BaseTax]
|
List of taxes to finance into the principal. |
required |
tz
|
tzinfo
|
Time zone used when resolving calendar dates for tax calculation. |
required |
Returns:
| Type | Description |
|---|---|
GrossupResult
|
GrossupResult with the grossed-up principal, tax breakdown, and a |
GrossupResult
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If requested_amount is not positive, no taxes provided, or the solver fails to converge. |
grossup_loan(requested_amount, interest_rate, due_dates, disbursement_date, scheduler, taxes, tz, **loan_kwargs)
¶
Compute a grossed-up loan in a single call.
Sugar for grossup(...).to_loan(**loan_kwargs). The borrower wants to
receive requested_amount; this function finds the principal that
satisfies principal - tax = requested_amount and returns a fully
configured Loan.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
requested_amount
|
Money
|
The net amount the borrower wants to receive. |
required |
interest_rate
|
InterestRate
|
The loan interest rate. |
required |
due_dates
|
list[date]
|
Payment due dates. |
required |
disbursement_date
|
datetime
|
When the loan is disbursed. |
required |
scheduler
|
type[BaseScheduler]
|
Scheduler class for generating the amortization schedule. |
required |
taxes
|
list[BaseTax]
|
List of taxes to finance into the principal. |
required |
tz
|
tzinfo
|
Time zone used when resolving calendar dates for tax calculation. |
required |
**loan_kwargs
|
Any
|
Extra keyword arguments forwarded to the Loan constructor (e.g. fine_rate, grace_period_days, mora_interest_rate, mora_strategy). |
{}
|
Returns:
| Type | Description |
|---|---|
Loan
|
A Loan with the grossed-up principal and taxes attached. |
grossup_loan¶
money_warp.grossup_loan(requested_amount, interest_rate, due_dates, disbursement_date, scheduler, taxes, tz, **loan_kwargs)
¶
Compute a grossed-up loan in a single call.
Sugar for grossup(...).to_loan(**loan_kwargs). The borrower wants to
receive requested_amount; this function finds the principal that
satisfies principal - tax = requested_amount and returns a fully
configured Loan.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
requested_amount
|
Money
|
The net amount the borrower wants to receive. |
required |
interest_rate
|
InterestRate
|
The loan interest rate. |
required |
due_dates
|
list[date]
|
Payment due dates. |
required |
disbursement_date
|
datetime
|
When the loan is disbursed. |
required |
scheduler
|
type[BaseScheduler]
|
Scheduler class for generating the amortization schedule. |
required |
taxes
|
list[BaseTax]
|
List of taxes to finance into the principal. |
required |
tz
|
tzinfo
|
Time zone used when resolving calendar dates for tax calculation. |
required |
**loan_kwargs
|
Any
|
Extra keyword arguments forwarded to the Loan constructor (e.g. fine_rate, grace_period_days, mora_interest_rate, mora_strategy). |
{}
|
Returns:
| Type | Description |
|---|---|
Loan
|
A Loan with the grossed-up principal and taxes attached. |
GrossupResult¶
money_warp.GrossupResult
¶
Result of a grossup calculation.
Carries the grossed-up principal, the original requested amount, the
computed tax, and all the parameters needed to construct a Loan via
the convenience method to_loan().
Attributes:
| Name | Type | Description |
|---|---|---|
principal |
The grossed-up principal (loan amount including financed tax). |
|
requested_amount |
The amount the borrower actually receives. |
|
total_tax |
Total tax computed on the grossed-up principal. |
Functions¶
to_loan(**loan_kwargs)
¶
Create a Loan from this grossup result.
All schedule parameters (principal, interest_rate, due_dates,
disbursement_date, scheduler, taxes) are forwarded automatically.
Pass any additional Loan keyword arguments (fine_rate,
grace_period_days, mora_interest_rate, mora_strategy) via
loan_kwargs.
Returns:
| Type | Description |
|---|---|
Loan
|
A fully configured Loan with the grossed-up principal. |