Skip to content

Commit a99afef

Browse files
Code refactoring
1 parent 95f8a30 commit a99afef

36 files changed

Lines changed: 1020 additions & 51 deletions

Makefile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
# ================================================
1212

1313
# Configuration
14-
PYTHON := python3.11
15-
#DOCTEST_FILES := $(wildcard curo/daycount/*.py docs/examples/*.md curo/calculator.py)
16-
DOCTEST_FILES := $(wildcard docs/tests/example_01.py)
14+
PYTHON := python3
15+
DOCTEST_FILES := $(wildcard curo/daycount/*.py curo/calculator.py docs/tests/*.py)
1716
VENV_DIR := .venv
1817
UV := uv
1918

curo/calculator.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,11 @@ def solve_rate(
217217
UnsolvableError: If no rate can be found within bounds.
218218
219219
Notes:
220+
- If called after solve_value, uses the existing profile (if it has a 'factor' column)
221+
and sets all is_known to True to ensure validation passes for SOLVE_RATE mode.
222+
- Always assigns day count factors based on the provided convention, as it may differ
223+
from the convention used in solve_value.
220224
- Uses scipy.optimize.brentq for root-finding within [-0.9999, upper_bound].
221-
- Upper bound is configurable to handle extreme APRs (e.g., usurious loans > 1000%).
222-
- Very large upper bounds (e.g., > 100.0) may slow convergence or cause
223-
numerical issues.
224225
- For USAppendixJ, the periodic rate is annualized by multiplying by periods_in_year.
225226
"""
226227
if upper_bound <= 0.0:
@@ -230,11 +231,20 @@ def solve_rate(
230231
if self._is_bespoke_profile:
231232
if not isinstance(self.profile, pd.DataFrame) or self.profile.empty:
232233
raise ValidationError("Bespoke profile must be a non-empty DataFrame")
233-
cash_flows = self.profile
234+
cash_flows = self.profile.copy()
234235
else:
235-
if not self._series:
236-
raise ValidationError("No cash flow series provided")
237-
cash_flows = self._build_profile(start_date)
236+
# Check if profile exists and has a 'factor' column (indicating solve_value was called)
237+
if (self.profile is not None and
238+
isinstance(self.profile, pd.DataFrame) and
239+
not self.profile.empty and
240+
ColumnExtras.FACTOR.value in self.profile.columns):
241+
cash_flows = self.profile.copy() # Use existing profile
242+
# Set all is_known to True to pass SOLVE_RATE validation
243+
cash_flows[Column.IS_KNOWN.value] = True
244+
else:
245+
if not self._series:
246+
raise ValidationError("No cash flow series provided")
247+
cash_flows = self._build_profile(start_date)
238248

239249
sort_by = SortColumn.POST_DATE if convention.use_post_dates else SortColumn.VALUE_DATE
240250
cash_flows = self._sort_cash_flows(cash_flows, sort_by=sort_by)
@@ -244,7 +254,7 @@ def solve_rate(
244254
mode=ValidationMode.SOLVE_RATE
245255
)
246256

247-
# Assign day count factors
257+
# Assign day count factors based on the provided convention
248258
cash_flows = self._assign_factors(cash_flows, convention)
249259

250260
# Define NFV function for root-finding
@@ -387,8 +397,8 @@ def build_schedule(
387397
continue # Skip charges
388398
amount = row[Column.AMOUNT.value]
389399
interest = row[ColumnExtras.INTEREST.value]
390-
capital = amount - interest
391-
capital_balance += interest + amount # Amount includes sign (negative for advances)
400+
capital = amount + interest
401+
capital_balance += interest + amount
392402

393403
schedule.at[
394404
idx, ColumnExtras.CAPITAL.value

docs/assets/images/cfd_01.png

-18.4 KB
Binary file not shown.

docs/assets/images/cfd_02.png

-20.4 KB
Binary file not shown.

docs/assets/images/cfd_03.png

-18.3 KB
Binary file not shown.

docs/assets/images/cfd_04.png

-21.7 KB
Binary file not shown.

docs/assets/images/cfd_05.png

-17.5 KB
Binary file not shown.

docs/assets/images/cfd_06.png

-27.9 KB
Binary file not shown.

docs/assets/images/cfd_07.png

-27.9 KB
Binary file not shown.

docs/assets/images/cfd_08.png

-23.9 KB
Binary file not shown.

0 commit comments

Comments
 (0)