Skip to content

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.

Functions
resolve()

Return the active entry at self.now(), or None if deleted.

update(effective_date, new_entry)

From effective_date onward this item resolves to new_entry.

delete(effective_date)

From effective_date onward this item resolves to None.

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 _time_ctx attribute.

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 _time_ctx attribute

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

Warped time source that returns a fixed time for time travel scenarios.

Functions
now()

Return the fixed datetime instead of current time.

date()

Return the fixed date instead of current date.

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

money_warp.InvalidDateError

Bases: WarpError

Raised when an invalid date is provided to Warp.

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.

Attributes
total_paid property

Sum of all payment components (fine + interest + mora + principal).

Allocation

money_warp.Allocation dataclass

Breakdown of a payment's allocation to a single installment.

Each allocation shows how much principal, interest, mora, and fine from a payment were attributed to a specific installment.

Attributes
total_allocated property

Sum of all components allocated to this installment.

Data Classes

PaymentSchedule

money_warp.PaymentSchedule dataclass

Complete payment schedule for a loan.

Contains all payment entries and summary information.

Functions
__post_init__()

Calculate totals after initialization.

__len__()

Number of payments in the schedule.

__getitem__(index)

Get payment entry by index.

__iter__()

Iterate over payment entries.

__str__()

String representation of the schedule.

PaymentScheduleEntry

money_warp.PaymentScheduleEntry dataclass

Represents a single payment in an amortization schedule.

This is the standard structure that all schedulers should return.

Functions
__str__()

String representation of the payment entry.

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:

>>> from money_warp import Money, InterestRate, present_value_of_annuity
>>>
>>> # PV of $1000 monthly payments for 12 months at 5% annual
>>> monthly_rate = InterestRate("5% annual").to_monthly()
>>> pv = present_value_of_annuity(Money("1000"), monthly_rate, 12)
>>> print(f"PV of annuity: {pv}")
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:

>>> from money_warp import Money, InterestRate, present_value_of_perpetuity
>>>
>>> # PV of $100 annual payments forever at 5%
>>> pv = present_value_of_perpetuity(Money("100"), InterestRate("5% annual"))
>>> print(f"PV of perpetuity: {pv}")  # Should be $2000
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:

>>> from money_warp import InterestRate, discount_factor
>>>
>>> # Discount factor for 5% over 2 years
>>> df = discount_factor(InterestRate("5% annual"), 2)
>>> print(f"Discount factor: {df}")  # Should be about 0.907
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:

>>> from money_warp import Money, InterestRate, present_value_of_annuity
>>>
>>> # PV of $1000 monthly payments for 12 months at 5% annual
>>> monthly_rate = InterestRate("5% annual").to_monthly()
>>> pv = present_value_of_annuity(Money("1000"), monthly_rate, 12)
>>> print(f"PV of annuity: {pv}")

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:

>>> from money_warp import Money, InterestRate, present_value_of_perpetuity
>>>
>>> # PV of $100 annual payments forever at 5%
>>> pv = present_value_of_perpetuity(Money("100"), InterestRate("5% annual"))
>>> print(f"PV of perpetuity: {pv}")  # Should be $2000

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:

>>> from money_warp import InterestRate, discount_factor
>>>
>>> # Discount factor for 5% over 2 years
>>> df = discount_factor(InterestRate("5% annual"), 2)
>>> print(f"Discount factor: {df}")  # Should be about 0.907

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:

>>> from datetime import datetime
>>> dates = generate_monthly_dates(datetime(2024, 1, 15), 3)
>>> # Returns [2024-01-15, 2024-02-15, 2024-03-15]

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:

>>> from datetime import datetime
>>> dates = generate_biweekly_dates(datetime(2024, 1, 1), 4)
>>> # Returns [2024-01-01, 2024-01-15, 2024-01-29, 2024-02-12]

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:

>>> from datetime import datetime
>>> dates = generate_weekly_dates(datetime(2024, 1, 1), 4)
>>> # Returns [2024-01-01, 2024-01-08, 2024-01-15, 2024-01-22]

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:

>>> from datetime import datetime
>>> dates = generate_quarterly_dates(datetime(2024, 1, 15), 4)
>>> # Returns [2024-01-15, 2024-04-15, 2024-07-15, 2024-10-15]

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:

>>> from datetime import datetime
>>> dates = generate_annual_dates(datetime(2024, 1, 15), 3)
>>> # Returns [2024-01-15, 2025-01-15, 2026-01-15]

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:

>>> from datetime import datetime
>>> dates = generate_custom_interval_dates(datetime(2024, 1, 1), 4, 10)
>>> # Returns payments every 10 days: [2024-01-01, 2024-01-11, 2024-01-21, 2024-01-31]

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. "America/Sao_Paulo") or a tzinfo instance.

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:

  • datetime values are passed through :func:ensure_aware.
  • list values whose first element is a datetime are 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.

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.

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

to_loan() convenience method.

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.