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).

Percentage

money_warp.Percentage

A non-negative percentage applied flat over a value.

Construction accepts only strings in the format "<number>%". Numeric inputs and strings without % are rejected to eliminate the classic 5 vs 0.05 ambiguity.

Parameters:

Name Type Description Default
rate str

Percentage as a string in the format "<number>%" (e.g. "5%", "5.5%", "0.5%", "5.000%").

required
precision Optional[int]

Default number of decimal places used by :meth:as_decimal / :meth:as_percentage when no explicit precision is passed at call time. None keeps full precision.

None
rounding str

Rounding mode from the :mod:decimal module (e.g. ROUND_HALF_UP, ROUND_DOWN). Used only when precision is set.

ROUND_HALF_UP
str_decimals int

Number of decimal places for the percentage in :meth:__str__. Default 2 gives "5.00%".

2

Raises:

Type Description
TypeError

If rate is not a string.

ValueError

If the string is not a valid percentage format, contains a temporal suffix (use :class:Rate/:class:InterestRate for those), or represents a negative value.

Examples:

>>> p = Percentage("5%")
>>> p.as_decimal()
Decimal('0.05')
>>> p.as_percentage()
Decimal('5')
>>> str(p)
'5.00%'
Functions
as_decimal(precision=None)

Return the percentage as a decimal (0.05 for 5%).

Parameters:

Name Type Description Default
precision Optional[int]

Number of decimal places. None falls back to the precision passed at construction; if both are None, the raw value is returned without quantization.

None
as_percentage(precision=None)

Return the percentage as a percentage value (5 for 5%).

Parameters:

Name Type Description Default
precision Optional[int]

Number of decimal places. None falls back to the precision passed at construction; if both are None, the raw value is returned without quantization.

None
__str__()

Canonical string representation: "5.00%".

Round-trips through Percentage(str(p)).

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

Bases: BaseLoan

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
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, waive_fines=False, waive_mora=False, waive_overdue_interest=False, discount=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
waive_fines bool

If True, all accumulated fines up to this payment are forgiven. Future fines can still accrue.

False
waive_mora bool

If True, all accrued mora interest up to this payment is forgiven. Future mora can still accrue.

False
waive_overdue_interest bool

If True, regular interest accrued between the due date and the payment date is forgiven.

False
discount Optional[Money]

Flat amount to forgive from obligations before allocating the payment. Follows the same priority as payment allocation (fines -> mora -> interest -> principal).

None

Returns:

Type Description
Settlement

Settlement describing how the payment was allocated.

anticipate_payment(amount, installments=None, description=None, waive_fines=False, waive_mora=False, waive_overdue_interest=False, discount=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.

get_expected_payment_amount(due_date)

Get the expected payment amount for a specific due date.

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, what has been covered by a discount, and the detailed per-payment allocations.

Installments are derived views -- the Loan builds them on demand from the CashFlow and the schedule.

balance_tolerance controls the sub-cent threshold under which a positive residual is treated as zero for balance and is_fully_paid. Defaults to R$0.01 (matching DEFAULT_BALANCE_TOLERANCE); :class:Loan and :class:BillingCycleLoan propagate their own configurable value through every installment snapshot they build.

The *_discounted fields aggregate the discount portion each allocation absorbed for that component. They default to zero so discount-free payments produce identical installments to before.

Attributes
total_discounted property

Sum of discount portions absorbed by this installment.

balance property

The amount still owed to fully settle this installment.

Positive residuals within balance_tolerance collapse to zero so is_fully_paid agrees with Allocation.is_fully_covered on rounding artifacts that the tolerance-adjustment mechanism absorbs. The discount portions are treated as covered, mirroring what Settlement.discount_applied reports at the loan level.

is_fully_paid property

Whether this installment has been fully settled.

Uses balance, which absorbs sub-cent residuals within :attr:balance_tolerance. A R\(0.005 leftover therefore counts as fully paid; a R\)0.02 leftover does not (at the default tolerance).

Functions
from_schedule_entry(entry, allocations, expected_mora, expected_fine, balance_tolerance=DEFAULT_BALANCE_TOLERANCE) classmethod

Build an Installment from a scheduler's PaymentScheduleEntry.

balance_tolerance is forwarded to the constructed Installment so balance and is_fully_paid honor the loan-level setting. The *_discounted fields are derived from the allocations so callers never need to compute them.

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, plus how much of the payment's discount covered each component. The discount fields default to zero so payments without a discount keep their original shape.

Attributes
total_allocated property

Sum of all components allocated to this installment.

total_discounted property

Sum of discount portions absorbed by this installment.

Mirrors :attr:total_allocated for the payment's discount contribution. A payment without a discount has every component at zero.

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.

Rate

Rate

money_warp.Rate

General-purpose financial rate with explicit decimal/percentage handling.

Supports signed values (positive and negative), period conversions, and string parsing. Use this type for computed metrics like IRR and MIRR. For contractual interest rates that must be non-negative, use InterestRate.

Attributes
year_size property

Day-count convention used for daily conversions.

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 rate.

Parameters:

Name Type Description Default
rate Union[str, Decimal, float]

Rate as string ("5.25% a", "-2.5% a", "0.004167 m") or numeric value. Abbreviated formats ("5.25% a.a.") are also accepted and automatically set str_style to "abbrev".

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. Example: {CompoundingFrequency.MONTHLY: "a.m"} removes the trailing dot for monthly only.

None
as_decimal(precision=None)

Get as decimal (0.05 = 5%).

Parameters:

Name Type Description Default
precision Optional[int]

Number of decimal places. None returns the raw value.

None

Returns:

Type Description
Decimal

The rate as a Decimal, optionally quantized.

as_percentage(precision=None)

Get as percentage (5.0 = 5%).

Parameters:

Name Type Description Default
precision Optional[int]

Number of decimal places. None returns the raw value.

None

Returns:

Type Description
Decimal

The rate as a percentage Decimal, optionally quantized.

as_float(precision=None)

Get as a float (0.05 = 5%).

Parameters:

Name Type Description Default
precision Optional[int]

Number of decimal places to round to. None returns the unrounded float conversion.

None

Returns:

Type Description
float

The rate as a float, optionally rounded.

to_daily()

Convert to daily rate.

to_monthly()

Convert to monthly rate.

to_annual()

Convert to annual rate.

to_periodic_rate(num_periods)

Convert to periodic rate for given number of periods per year.

Parameters:

Name Type Description Default
num_periods int

Number of periods per year (e.g., 12 for monthly)

required

Returns:

Name Type Description
Decimal Decimal

Periodic rate as decimal

__str__()

Clear string representation.

__repr__()

Developer representation.

__eq__(other)

Compare rates by converting to effective annual.

__lt__(other)

Less than comparison using effective annual rates.

__le__(other)

Less than or equal comparison using effective annual rates.

__gt__(other)

Greater than comparison using effective annual rates.

__ge__(other)

Greater than or equal comparison using effective annual rates.

Billing Cycle Loan

BillingCycleLoan

money_warp.BillingCycleLoan

Bases: BaseLoan

Loan with fixed amortization and credit-card-like billing cycles.

Combines a traditional amortization schedule (Price / SAC) with billing-cycle timing (monthly close + due date) and a mora interest rate that can change per cycle via a callable resolver.

The CashFlow is the single source of truth, just like Loan. Settlements, installments, balances, and fines are all derived on demand by a forward pass.

Parameters:

Name Type Description Default
principal Money

Loan principal amount (must be positive).

required
interest_rate InterestRate

Annual contractual interest rate.

required
billing_cycle BaseBillingCycle

Billing cycle factory that generates closing and due dates.

required
start_date datetime

Start of the first billing period. Closing dates are generated after this date.

required
num_installments int

Number of installments in the amortization.

required
disbursement_date Optional[datetime]

When funds are released. Defaults to now(). Must be before the first due date.

None
scheduler Optional[Type[BaseScheduler]]

Amortization strategy. Defaults to :class:PriceScheduler.

None
fine_rate Optional[InterestRate]

Rate for computing fines on missed payments. Defaults to 2% annual.

None
grace_period_days int

Days after due date before fines apply.

0
mora_interest_rate Optional[InterestRate]

Base mora rate. Defaults to interest_rate.

None
mora_rate_resolver Optional[MoraRateResolver]

Optional callable that adjusts the base mora rate per billing cycle. Receives (closing_date, base_mora_rate) and returns the effective InterestRate for that cycle.

None
mora_strategy MoraStrategy

How mora interest compounds. Defaults to :attr:MoraStrategy.COMPOUND.

COMPOUND
Attributes
closing_dates property

Closing dates for each billing period.

statements property

Billing-period statements (one per cycle).

Functions
record_payment(amount, payment_date, interest_date=None, description=None, waive_fines=False, waive_mora=False, waive_overdue_interest=False, discount=None)

Record a payment and return the derived settlement.

is_late(due_date, as_of_date=None)

Check if a payment is late considering the grace period.

MoraRateResolver

money_warp.MoraRateResolver

Bases: Protocol

Callable that returns the mora rate for a given billing cycle.

Receives the cycle's reference date (typically the closing date) and the base mora rate configured on the loan. Returns the InterestRate to use for mora computation in that cycle.

Implementations are free to ignore the base rate and return an entirely independent value, or to adjust it (e.g. add a spread on top of an external index).

BaseBillingCycle

money_warp.BaseBillingCycle

Bases: ABC

Abstract factory for billing cycle date generation and statement building.

Subclasses define how statement closing dates and payment due dates are derived. The credit card uses whichever implementation is injected at construction time — same pattern as BaseScheduler on the Loan.

Statement building is concrete: it relies on the abstract date methods and the cash-flow query API, so it works for every implementation.

Parameters:

Name Type Description Default
due_dates Optional[List[date]]

Optional explicit due dates. When provided, these override the dates that would be computed from closing dates via :meth:due_date_for. Useful when the payment schedule has non-standard due dates that don't follow the closing-day offset rule.

None
Attributes
explicit_due_dates property

Explicit due dates provided at construction, or None.

Functions
closing_dates_between(start, end) abstractmethod

Return closing dates for all complete cycles in [start, end].

The first closing date is the earliest one strictly after start. The last closing date is the latest one at or before end.

due_date_for(closing_date) abstractmethod

Payment due date for a given statement closing date.

due_dates_between(start, end, tz)

Return payment due dates for all cycles in [start, end].

When explicit due_dates were provided at construction, returns those falling strictly after start and at or before end. Otherwise derives them from :meth:closing_dates_between and :meth:due_date_for.

build_statements(cash_flow, opening_date, end_date, minimum_payment_rate, minimum_payment_floor)

Build statements for all closed cycles in [opening_date, end_date].

Iterates the closing dates, slices the cash_flow by period, and produces a :class:Statement for each one. Balance is carried forward iteratively across periods.

compute_minimum_payment(closing_balance, rate, floor) staticmethod

Minimum payment for a given closing balance.

MonthlyBillingCycle

money_warp.MonthlyBillingCycle

Bases: BaseBillingCycle

Billing cycle that closes on a fixed calendar day every month.

Parameters:

Name Type Description Default
closing_day int

Day of month (1-28) when the statement closes.

1
payment_due_days int

Number of days after closing for the payment due date. Defaults to 15.

15
due_dates Optional[List[date]]

Optional explicit due dates that override the computed ones. See :class:BaseBillingCycle for details.

None
Functions
closing_dates_between(start, end)

Return monthly closing dates strictly after start up to end.

BillingCycleLoanStatement

money_warp.BillingCycleLoanStatement dataclass

Snapshot of a single billing period for a billing-cycle loan.

Combines the amortization schedule view (expected principal / interest) with the actual payment activity and any mora / fine charges for the period.

Credit Card

CreditCard

money_warp.CreditCard

Revolving credit instrument with periodic billing statements.

The credit card is a cash flow. Transactions (purchases, payments, refunds) are added directly to the underlying :class:CashFlow. Statements, interest charges, and fines all emerge from that cash flow — they are never stored as independent state. Billing-cycle processing materialises interest and fine items lazily, controlled by an idempotency counter.

Parameters:

Name Type Description Default
interest_rate InterestRate

Annual rate applied to carried balances.

required
billing_cycle Optional[BaseBillingCycle]

Strategy that generates closing / due dates. Defaults to MonthlyBillingCycle().

None
minimum_payment_rate Optional[Decimal]

Fraction of the closing balance required as minimum payment (default 0.15 = 15 %).

None
minimum_payment_floor Optional[Money]

Absolute floor for the minimum payment (default $25).

None
fine_rate Optional[InterestRate]

Fine rate applied to the minimum payment when the minimum is not met (default 2% annual).

None
opening_date Optional[datetime]

When the card was opened. Defaults to now().

None
credit_limit Optional[Money]

Maximum outstanding balance. None means unlimited.

None
Attributes
current_balance property

Outstanding balance as of now(), after closing due cycles.

available_credit property

Remaining credit if a limit is set, else None.

is_paid_off property

Whether the balance is zero (or overpaid).

statements property

All closed billing-period statements up to now().

Functions
now()

Current datetime (Warp-aware via shared TimeContext).

purchase(amount, date=None, description=None)

Record a purchase on the card.

Parameters:

Name Type Description Default
amount Money

Purchase amount (positive).

required
date Optional[datetime]

Transaction date. Defaults to now().

None
description Optional[str]

Optional merchant / memo.

None

Raises:

Type Description
ValueError

If amount is not positive or exceeds the available credit.

pay(amount, date=None, description=None)

Record a payment toward the card balance.

Parameters:

Name Type Description Default
amount Money

Payment amount (positive).

required
date Optional[datetime]

Transaction date. Defaults to now().

None
description Optional[str]

Optional memo.

None

Raises:

Type Description
ValueError

If amount is not positive.

refund(amount, date=None, description=None)

Record a merchant refund.

Parameters:

Name Type Description Default
amount Money

Refund amount (positive).

required
date Optional[datetime]

Transaction date. Defaults to now().

None
description Optional[str]

Optional memo.

None

Raises:

Type Description
ValueError

If amount is not positive.

get_cash_flow()

Return a signed cash-flow view of all resolved transactions.

Debits (purchases, interest, fines) are positive. Credits (payments, refunds) are negated to negative. Items are sorted by datetime.

Statement

money_warp.Statement dataclass

A single billing-period statement.

Statements are derived views built by the billing cycle from a cash flow — they describe what happened during one period.

Attributes
is_minimum_met property

Whether total payments in this period met the minimum requirement.

Working Day Calendars

WorkingDayCalendar

money_warp.WorkingDayCalendar

Bases: Protocol

Protocol for working-day determination.

Implementations define which days are working days and how to find the next working day from a given date.

Functions
is_working_day(d)

Return True if d is a working day.

next_working_day(d)

Return the next working day strictly after d.

BrazilianWorkingDayCalendar

money_warp.BrazilianWorkingDayCalendar

Brazilian working-day calendar with national holidays.

Weekends and all Brazilian national holidays are non-working days. Optionally accepts extra holiday dates for state/municipal holidays.

National holidays (fixed): - Jan 1: Confraternização Universal (New Year) - Apr 21: Tiradentes - May 1: Dia do Trabalhador (Labour Day) - Sep 7: Independência do Brasil - Oct 12: Nossa Senhora Aparecida - Nov 2: Finados (All Souls' Day) - Nov 15: Proclamação da República - Nov 20: Dia da Consciência Negra (Black Consciousness Day) - Dec 25: Natal (Christmas)

National holidays (movable, computed from Easter): - Carnival Monday: Easter - 48 days - Carnival Tuesday: Easter - 47 days - Good Friday: Easter - 2 days - Corpus Christi: Easter + 60 days

EveryDayCalendar

money_warp.EveryDayCalendar

Calendar where every day is a working day.

This is the default calendar for loans — no penalty deferral ever occurs. Equivalent to the behavior before working-day support was introduced.

WeekendCalendar

money_warp.WeekendCalendar

Calendar where Saturdays and Sundays are non-working days.

Engines

MoraStrategy

money_warp.MoraStrategy

Bases: Enum

Strategy for computing mora (late) interest.

SIMPLE: mora rate is applied to the outstanding principal only. COMPOUND: mora rate is applied to principal + accrued regular interest.

Time Context

TimeContext

money_warp.TimeContext

Shared, overridable time source with a per-loan timezone.

Default behaviour delegates to :data:default_time_source (real wall-clock time). Call :meth:override to swap the source — for example with a WarpedTime instance inside a Warp context.

The tz attribute controls which timezone is used when extracting calendar dates from UTC datetimes (via :meth:to_date and :meth:to_datetime). It defaults to :func:get_tz.

Functions
to_date(dt)

Extract calendar date in this context's business timezone.

to_datetime(d)

Convert calendar date to UTC datetime (midnight in business tz).

Additional Models

AnticipationResult

money_warp.AnticipationResult dataclass

Result of an anticipation calculation.

Returned by :meth:Loan.calculate_anticipation. Contains the amount the borrower should pay today and the installments being removed.

Cash Flow Types

CashFlowEntry

money_warp.CashFlowEntry dataclass

Bases: ABC

Abstract base for a monetary movement at a point in time.

Use :class:ExpectedCashFlowEntry for projected items (e.g. a loan schedule) and :class:HappenedCashFlowEntry for recorded facts (e.g. an actual payment).

Time-awareness and versioning live in :class:CashFlowItem, which wraps one or more CashFlowEntry instances in a timeline.

category is a frozenset of string tags. A single string is normalized to frozenset({string}) by :class:CashFlowItem.

interest_date is an optional secondary date used by loan payments to indicate the cutoff for interest accrual. When None, the entry's datetime is used as the interest accrual cutoff.

ExpectedCashFlowEntry

money_warp.ExpectedCashFlowEntry dataclass

Bases: CashFlowEntry

A projected cash flow that has not occurred yet.

HappenedCashFlowEntry

money_warp.HappenedCashFlowEntry dataclass

Bases: CashFlowEntry

A recorded cash flow that has already occurred.

CashFlowType

money_warp.CashFlowType

Bases: str, Enum

Whether a cash-flow entry is a projection or a recorded fact.