|
1 | | -# Curo |
| 1 | +# Curo Python |
2 | 2 |
|
3 | | - |
| 3 | + |
4 | 4 | [](https://codecov.io/gh/andrewmurphy353/curo_python) |
5 | 5 |  |
6 | 6 |
|
| 7 | +Curo Python is a powerful, open-source library for performing instalment credit financial calculations, from simple loans to complex leasing and hire purchase agreements. Built from the ground up in Python, it leverages pandas DataFrames for cash flow management and SciPy for efficient solving of unknown values or rates using Brent's method. |
7 | 8 |
|
8 | | -Introducing Curo Python, a comprehensive library designed for performing both simple and complex instalment credit financial calculations. Explore its capabilities in action with **[Curo Calculator](https://curocalc.app)**, an application built on the Curo Dart library. |
| 9 | +Explore the [documentation](https://andrewmurphy353.github.io/curo_python/), or try it in action with the [Curo Calculator](https://curocalc.app), a Flutter app showcasing similar functionality. |
9 | 10 |
|
10 | | -...add comment that this is a complete rewrite of Curo Dart to leverage the Python pandas and Scipy libraries (add after first sentence) |
| 11 | +## Why Curo Python? |
11 | 12 |
|
12 | | -## Overview |
| 13 | +Curo Python is designed for developers and financial professionals who need robust tools for fixed-term credit calculations. It goes beyond basic financial algebra, offering features typically found in commercial software, such as: |
13 | 14 |
|
14 | | -This financial calculator library is for solving unknown cash flow values and unknown interest rates implicit in fixed-term instalment credit products, for example leasing, loans and hire purchase contracts [^1], and incorporates features that are likely to be found only in commercially developed software. It has been designed for use in applications with requirements that extend beyond what can be achieved using standard financial algebra. |
| 15 | +- Solving for unknown cash flow values (e.g., instalment amounts). |
| 16 | +- Calculating implicit interest rates (e.g., IRR or APR). |
| 17 | +- Support for multiple day count conventions, including EU and US regulatory standards. |
| 18 | +- Flexible handling of cash flow series for loans, leases, and investment scenarios. |
15 | 19 |
|
16 | | -For an introduction to many of the features be sure to check out the GitHub repository [examples](https://github.com/andrewmurphy353/curo_python/tree/main/example) and the accompanying cash flow diagrams which pictorially represent the cash inflows and outflows [^2] of each example. |
| 20 | +Check out the [examples folder](https://github.com/andrewmurphy353/curo_python/tree/main/examples) for practical use cases and accompanying cash flow diagrams that visualize inflows and outflows. |
17 | 21 |
|
18 | | -Using the calculator is straightforward as the following examples demonstrate. |
| 22 | +## Getting Started |
19 | 23 |
|
20 | | -### Example using `solve_value(...)` to find an unknown cash flow value: |
| 24 | +### Installation |
| 25 | + |
| 26 | +Install Curo Python using your preferred package manager: |
| 27 | + |
| 28 | +With **pip**: |
| 29 | +```shell |
| 30 | +$ pip install --user curo |
| 31 | +``` |
| 32 | +With **uv**: |
| 33 | +```shell |
| 34 | +$ uv add curo |
| 35 | +``` |
| 36 | + |
| 37 | +### Basic Usage |
| 38 | + |
| 39 | +Curo Python makes financial calculations intuitive. Below are two examples demonstrating how to solve for an unknown cash flow value and an implicit interest rate. |
| 40 | + |
| 41 | +**Example 1: Solving for an Unknown Cash Flow Value** |
| 42 | + |
| 43 | +Calculate the monthly instalment for a $10,000 loan over 6 months at an 8.25% annual interest rate. |
21 | 44 |
|
22 | 45 | ```python |
23 | | -# Step 1: Instantiate the calculator |
| 46 | +from curo import Calculator, SeriesAdvance, SeriesPayment, Mode, US30360 |
| 47 | + |
| 48 | +# Step 1: Create a calculator instance |
24 | 49 | calculator = Calculator() |
25 | 50 |
|
26 | | -# Step 2: Define the advance, payment, and/or charge cash flow series |
| 51 | +# Step 2: Define cash flow series |
27 | 52 | calculator.add( |
28 | | - SeriesAdvance( |
29 | | - label = 'Loan', |
30 | | - amount = 10000.0, |
31 | | - )) |
| 53 | + SeriesAdvance(label="Loan", amount=10000.0) |
| 54 | +) |
32 | 55 | calculator.add( |
33 | | - SeriesPayment( |
34 | | - numberOf = 6, |
35 | | - label = "Instalment", |
36 | | - amount = None, # leave undefined or None when it is the unknown to solve |
37 | | - mode = Mode.ARREAR, |
38 | | - )) |
39 | | - |
40 | | -# 3. Calculate the unknown cash flow value (result = 1707.00 to 2 decimal places) |
41 | | -value_result = calculator.solve_value( |
42 | | - day_count = US30360(), |
43 | | - interest_rate = 0.0825) |
| 56 | + SeriesPayment( |
| 57 | + number_of=6, |
| 58 | + label="Instalment", |
| 59 | + amount=None, # Set to None for unknown value |
| 60 | + mode=Mode.ARREAR |
| 61 | + ) |
| 62 | +) |
| 63 | + |
| 64 | +# Step 3: Solve for the instalment amount |
| 65 | +result = calculator.solve_value( |
| 66 | + day_count=US30360(), |
| 67 | + interest_rate=0.0825 |
| 68 | +) |
| 69 | +print(f"Monthly instalment: ${result:.2f}") # Output: $1707.00 |
44 | 70 | ``` |
45 | | -In the 2nd step we set the payment series value to `None`. We could also simply omit the amount attribute. This is how the unknown cash flow values that are to be computed are identified, and is the protocol to be followed when defining the unknown cash flow values you wish to calculate. |
46 | 71 |
|
47 | | -In the 3rd and final step we invoke the `solve_value(...)` method, passing in a day count convention instance and the annual interest rate to use in the calculation, expressed as a decimal. |
| 72 | +**Example 2: Solving for the Implicit Interest Rate** |
48 | 73 |
|
49 | | -The various day count conventions available in this library are described in more detail below. |
| 74 | +Find the internal rate of return (IRR) for a €10,000 loan repaid in 6 monthly instalments of €1,707. |
50 | 75 |
|
51 | | -### Example using `solve_rate(...)` to find the implicit interest rate in a cash flow series: |
| 76 | +```python |
| 77 | +from curo import Calculator, SeriesAdvance, SeriesPayment, Mode, US30360, EU200848EC |
52 | 78 |
|
53 | | -```Python |
54 | | -# Step 1: Instantiate the calculator |
| 79 | +# Step 1: Create a calculator instance |
55 | 80 | calculator = Calculator() |
56 | 81 |
|
57 | | -# Step 2: Define the advance, payment, and/or charge cash flow series |
| 82 | +# Step 2: Define cash flow series |
58 | 83 | calculator.add( |
59 | | - SeriesAdvance( |
60 | | - label = 'Loan', |
61 | | - value = 10000.0, |
62 | | - )) |
| 84 | + SeriesAdvance(label="Loan", amount=10000.0) |
| 85 | +) |
63 | 86 | calculator.add( |
64 | | - SeriesPayment( |
65 | | - numberOf = 6, |
66 | | - label = 'Instalment', |
67 | | - value = 1707.00, |
68 | | - mode = Mode.ARREAR, |
69 | | - )) |
70 | | - |
71 | | -# 3. Calculate the IRR or Internal Rate of Return (result = 8.250040%) |
72 | | -irr_rate = calculator.solve_rate( |
73 | | - dayCount = const US30360()) |
74 | | - |
75 | | -# ...or the APR for regulated EU Consumer Credit agreements (result = 8.569257%) |
76 | | -apr_rate = calculator.solve_rate( |
77 | | - dayCount = const EU200848EC()) |
| 87 | + SeriesPayment( |
| 88 | + number_of=6, |
| 89 | + label="Instalment", |
| 90 | + amount=1707.00, |
| 91 | + mode=Mode.ARREAR |
| 92 | + ) |
| 93 | +) |
| 94 | + |
| 95 | +# Step 3: Calculate the IRR and APR |
| 96 | +irr = calculator.solve_rate(day_count=US30360()) |
| 97 | +apr = calculator.solve_rate(day_count=EU200848EC()) |
| 98 | + |
| 99 | +print(f"IRR: {irr * 100:.6f}%") # Output: 8.250040% |
| 100 | +print(f"APR: {apr * 100:.6f}%") # Output: 8.569257% |
78 | 101 | ``` |
79 | 102 |
|
| 103 | +## Key Features |
| 104 | + |
80 | 105 | ### Day Count Conventions |
81 | 106 |
|
82 | | -A day count convention is a key component of every financial calculation as it determines the method to be used in measuring the time interval between each cash flow in a series. |
| 107 | +Day count conventions determine how time intervals between cash flows are measured. Curo Python supports a wide range of conventions to meet global financial standards: |
83 | 108 |
|
84 | | -There are dozens of convention's defined but the more important ones supported by this calculator are as follows: |
| 109 | +Convention|Description |
| 110 | +:---------|:---------- |
| 111 | +Actual ISDA | Uses actual days, accounting for leap and non-leap year portions. |
| 112 | +Actual/360 | Counts actual days, assuming a 360-day year. |
| 113 | +Actual/365 | Counts actual days, assuming a 365-day year. |
| 114 | +EU 30/360 | Assumes 30-day months and a 360-day year, per EU standards. |
| 115 | +EU 2023/2225 | Compliant with EU Directive 2023/2225 for APR calculations in consumer credit. |
| 116 | +UK CONC App | Supports UK APRC calculations for consumer credit, secured or unsecured. |
| 117 | +US 30/360 | Default for many US calculations, using 30-day months and a 360-day year. |
| 118 | +US 30U/360 | Like US 30/360, but treats February days uniformly as 30 days. |
| 119 | +US Appendix J | Implements US Regulation Z, Appendix J for APR in closed-end credit. |
85 | 120 |
|
86 | | -Convention | Description |
87 | | ------------| ------------- |
88 | | -Actual ISDA | Convention accounts for actual days between cash flow dates based on the portion in a leap year and the portion in a non-leap year as [documented here](https://en.wikipedia.org/wiki/Day_count_convention#Actual/Actual_ISDA). |
89 | | -Actual/360 | Convention accounts for actual days between cash flow dates and considers a year to have 360 days as [documented here](https://en.wikipedia.org/wiki/Day_count_convention#Actual/360) |
90 | | -Actual/365 | Convention accounts for actual days between cash flow dates and considers a year to have 365 days as [documented here](https://en.wikipedia.org/wiki/Day_count_convention#Actual/365_Fixed). |
91 | | -EU 30/360 | Convention accounts for days between cash flow dates based on a 30 day month, 360 day year as [documented here](https://en.wikipedia.org/wiki/Day_count_convention#30E/360). |
92 | | -EU 2023/2225| Convention based on the time periods between cash flow dates and the initial drawdown date, expressed in days and/or whole weeks, months or years. This convention is used specifically in APR (Annual Percentage Rate) consumer credit calculations within the European Union and is compliant with Directive (EU) 2023/2225 [available here](https://eur-lex.europa.eu/eli/dir/2023/2225/oj/eng), and is backward compatible with European Union Directive 2008/48/EC since repealed. |
93 | | -UK CONC App (1.1 & 1.2) | Convention is used in the United Kingdom (UK) for computing the Annual Percentage Rate of Charge (APRC) for consumer credit agreements, under the Financial Services and Markets Act 2000 (FSMA 2000). This implementation supports two contexts based on whether borrowings are **secured on land** (see [FCA Handbook - CONC App 1.1](https://www.handbook.fca.org.uk/handbook/CONC/App/1/1.html)), or **not secured on land** (see [FCA Handbook - CONC App 1.2](https://www.handbook.fca.org.uk/handbook/CONC/App/1/2.html)). Refer to the class documentation for details on the day count rules. |
94 | | -US 30/360 | Convention accounts for days between cash flow dates based on a 30 day month, 360 day year as [documented here](https://en.wikipedia.org/wiki/Day_count_convention#30/360_US). This is the default convention used by the Hewlett Packard HP12C and similar financial calculators, so choose this convention when unsure as it is the defacto convention used in the majority of fixed-term credit calculations. |
95 | | -US 30U/360 | Convention accounts for days between cash flow dates as per US 30/360, except for the month of February where the 28th, and 29th in a leap-year, are treated as 30 days. Note, the use of U in the naming signifies Uniform, so can be read as 30 uniform days in a month. |
96 | | -US Appendix J | Convention implements the U.S. Regulation Z, Appendix J (Federal Calendar) day count method for calculating the APR for closed-end credit transactions, such as mortgages, under the Truth in Lending Act (TILA). It uses a 30-day month divisor for odd days and supports multiple unit-periods (monthly, weekly, daily, fortnightly), including leap year handling (e.g., February 29). Time intervals are computed as whole unit-periods ((t)) plus a fractional adjustment ((f)) for odd days, aligning with the discounting formula ( P_x / (1 + f \times i) (1 + i)^t ). Results are validated against the [FFIEC APR Calculator](https://www.ffiec.gov/examtools/FFIEC-Calculators/APR/#/accountdata). See [12 CFR Part 1026, Appendix J](https://www.ecfr.gov/current/title-12/chapter-X/part-1026/appendix-Appendix%20J%20to%20Part%201026) for details. |
| 121 | +For XIRR-style calculations (referencing the first drawdown date), pass `use_xirr_method=True` to the convention constructor. When used with `Actual/365`, this matches Microsoft Excel’s XIRR function. |
97 | 122 |
|
98 | | -All conventions, except EU 2023/2225, UK CONC App 1.1 and 1.2, and US Appendix J, will by default compute time intervals between cash flows with reference to the dates of adjacent cash flows. |
| 123 | +### Cash Flow Diagrams |
99 | 124 |
|
100 | | -To override this so that time intervals are computed with reference to the first drawdown date, as in XIRR (eXtended Internal Rate of Return) based calculations, simply pass `use_xirr_method = True` to the respective day count convention constructor (refer to the code documentation for details). |
| 125 | +Cash flow diagrams visually represent the timing and direction of financial transactions. For example, a €10,000 loan repaid in 6 monthly instalments would look like this: |
101 | 126 |
|
102 | | -When the Actual/365 convention is used in this manner, e.g. `Act365(use_xirr_method = True)` the XIRR result will equal that produced by the equivalent Microsoft Excel XIRR function. |
| 127 | + |
103 | 128 |
|
104 | | -## Installation |
105 | | - |
106 | | -With pip |
107 | | -```shell |
108 | | -$ pip install --user curo |
109 | | -``` |
110 | | -With uv |
111 | | -```shell |
112 | | -$ uv add curo |
113 | | -``` |
| 129 | +- **Down arrows**: Money received (e.g., loan advance). |
| 130 | +- **Up arrows**: Money paid (e.g., instalments). |
| 131 | +- **Time line**: Represents the contract term, divided into compounding periods. |
114 | 132 |
|
115 | 133 | ## License |
116 | 134 |
|
117 | 135 | Copyright © 2026, [Andrew Murphy](https://github.com/andrewmurphy353). |
118 | 136 | Released under the [MIT License](LICENSE). |
119 | 137 |
|
120 | | -### Footnotes |
121 | | ---- |
122 | | - |
123 | | -[^1] Whilst the library uses asset finance nomenclature, it is equally capable of solving problems in investment-type scenarios. |
124 | | - |
125 | | -[^2] A cash flow diagram is simply a pictorial representation of the timing and direction of financial transactions. |
126 | | - |
127 | | -The diagram begins with a horizontal line, called a time line. The line represents the duration or contract term, and is commonly divided into compounding periods. The exchange of money in the financial arrangement is depicted by vertical arrows. Money a lender receives is represented by an arrow pointing up from the point in the time line when the transaction occurs; money paid out by the lender is represented by an arrow pointing down. The collection of all up and down arrow cash flows are what is referred to throughout the calculator documentation as a cash flow series. |
128 | | - |
129 | | -To illustrate using the example above, that is a 10,000.00 loan repayable by 6 monthly instalments in arrears (due at the end of each compounding period), the cash flow diagram would resemble something like this: |
| 138 | +## Learn More |
130 | 139 |
|
131 | | - |
| 140 | +- **Examples**: Dive into practical use cases in the [examples folder](https://github.com/andrewmurphy353/curo_python/tree/main/examples). |
| 141 | +- **Documentation**: Refer to the code [documentation](https://andrewmurphy353.github.io/curo_python/) for detailed class and method descriptions. |
| 142 | +- **Issues & Contributions**: Report bugs or contribute on [GitHub](https://github.com/andrewmurphy353/curo_python/issues). |
0 commit comments