Skip to main content

amortizacion-engine

Motor puro y stateless de tablas de amortización en Python: 13 modalidades de crédito con precisión decimal.Decimal exacta, API mínima (simulate()), cero dependencias duras y resultados serializables (to_dict() / to_json()).

PyPI Python CI Coverage License

Tabla de contenidos

Caracteristicas clave

  • Precision decimal exacta: todo monto y tasa se calcula con decimal.Decimal, nunca float. El cuadre de capital está garantizado: sum(principal_component) == principal y saldo final en 0.00.
  • 13 modalidades de credito: interes simple, compuesto, sistema frances, aleman, americano, con enganche, pagos extraordinarios, sin intereses, tasa variable, pagos personalizados, periodo de gracia, cargos/seguros e indexado a inflacion.
  • API minima: una sola funcion de entrada, simulate(), con conversion de tipos amigable y semantica request XOR kwargs.
  • Cero dependencias duras: solo libreria estandar (extra opcional pydantic).
  • Stateless y determinista: funciones puras y dataclasses(frozen=True); las fechas siempre llegan como parametro, nunca datetime.now() implicito.
  • Serializable: to_dict() y to_json() en todos los resultados, sin dependencias externas (Decimal como str para roundtrip JSON exacto).
  • Extensible por Strategy: cada modalidad es una estrategia de AmortizationStrategy; anadir la modalidad 14 no rompe nada existente.
  • Errores propios: jerarquia FinancialEngineError con subclases accionables; validacion agresiva, nunca assert.

Instalacion

Requiere Python >= 3.10 y no tiene dependencias duras (solo libreria estandar; el extra pydantic es opcional).

pip install amortizacion-engine

Con soporte opcional de Pydantic (validacion adicional de los modelos):

pip install "amortizacion-engine[pydantic]"

Quickstart

Genera una tabla de amortizacion en menos de 10 lineas: un credito frances de 15 000 a 24 % anual en 12 meses.

from financial_engine import simulate, AmortizationMode

result = simulate(
    mode=AmortizationMode.FRENCH_FIXED_PAYMENT,
    principal="15000.00",
    annual_rate="0.24",
    periods=12,
    start_date="2026-08-09",
)
print(result.summary.total_interest)
for row in result.schedule:
    print(row.period, row.payment, row.principal_component, row.interest_component, row.balance)

Salida (interes total del credito y las 12 filas del calendario):

2020.68

1 1418.39 1118.39 300.00 13881.61
2 1418.39 1140.76 277.63 12740.85
3 1418.39 1163.57 254.82 11577.28
4 1418.39 1186.84 231.55 10390.44
5 1418.39 1210.58 207.81 9179.86
6 1418.39 1234.79 183.60 7945.07
7 1418.39 1259.49 158.90 6685.58
8 1418.39 1284.68 133.71 5400.90
9 1418.39 1310.37 108.02 4090.53
10 1418.39 1336.58 81.81 2753.95
11 1418.39 1363.31 55.08 1390.64
12 1418.39 1390.64 27.75 0.00

Cada fila del calendario es una PaymentRow inmutable con payment, principal_component, interest_component, fees_component, balance y extra_payment; el resumen (summary) agrega los totales.

Las 13 modalidades

Cada modalidad corresponde a un miembro del enum AmortizationMode y se implementa como una estrategia del patron Strategy.

Modalidad (AmortizationMode) Sistema Descripcion
SIMPLE_INTEREST Interes simple Interes simple sobre saldo insoluto, sin capitalizacion.
COMPOUND_INTEREST Interes compuesto Interes compuesto con capitalizacion periodica configurable (mensual, quincenal o anual).
FRENCH_FIXED_PAYMENT Sistema frances Cuota fija, amortizacion creciente, interes decreciente.
GERMAN_CONSTANT_AMORTIZATION Sistema aleman Amortizacion de capital constante, cuota decreciente.
AMERICAN_BULLET Sistema americano Solo interes en cada periodo, capital al vencimiento.
DOWN_PAYMENT_ADJUSTED Con enganche/anticipo Igual que frances/aleman pero descontando un enganche inicial del principal.
EXTRA_PAYMENTS Pagos extraordinarios Abonos a capital con dos submodalidades: reducir plazo o reducir cuota.
ZERO_INTEREST Sin intereses Tasa 0 %, divide el principal entre el numero de periodos.
VARIABLE_RATE Tasa variable Acepta un calendario de tasas distintas por periodo o rango de periodos.
CUSTOM_SCHEDULE Pagos personalizados El usuario define manualmente el monto de cada pago.
GRACE_PERIOD_DEFERRED Periodo de gracia N periodos iniciales sin pago de capital (solo interes, o totalmente diferido con capitalizacion).
FEES_AND_INSURANCE Con cargos/seguro Igual que frances/aleman sumando cargos periodicos fijos o porcentuales a cada cuota.
INDEXED_INFLATION Indexado a inflacion Ajusta el saldo o la cuota periodo a periodo segun un indice externo (UDI, INPC, etc.).

Comparativa rapida de sistemas

Los cuatro sistemas clasicos frente a frente. La base comun es un credito de 15 000 a 24 % anual en 12 meses; excepciones: AMERICAN_BULLET usa 10 000 a 12 % (como en los ejemplos de modalidad) y SIMPLE_INTEREST usa 1 200 a 12 % (tambien como en sus ejemplos), para mantener la coherencia con el resto del documento.

Sistema Cuota inicial Interes total Total pagado
Frances (FRENCH_FIXED_PAYMENT) 1418.39 2020.68 17020.68
Aleman (GERMAN_CONSTANT_AMORTIZATION) 1550.00 (1250 + 300) 1950.00 16950.00
Americano (AMERICAN_BULLET, 10 000 a 12 %) 100.00 1200.00 11200.00
Interes simple (SIMPLE_INTEREST, 1 200 a 12 %) 112.00 78.00 1278.00

Ejemplos por modalidad

Todos los ejemplos usan la API publica simulate() con los mismos datos de partida (start_date="2026-08-09") y los valores comentados son la salida real verificada contra la libreria.

1. SIMPLE_INTEREST — interes simple

r = simulate(
    mode=AmortizationMode.SIMPLE_INTEREST,
    principal="1200.00", annual_rate="0.12", periods=12,
    start_date="2026-08-09",
)
print(r.summary.total_interest)          # 78.00
print(r.summary.total_paid)              # 1278.00
print(r.schedule[0].interest_component)  # 12.00 (decrece hasta 1.00)

2. COMPOUND_INTEREST — interes compuesto (zero-coupon)

r = simulate(
    mode=AmortizationMode.COMPOUND_INTEREST,
    principal="1000.00", annual_rate="0.12", periods=12,
    start_date="2026-08-09",
)
print(r.summary.total_interest)  # 126.84
print(r.schedule[-1].payment)    # 1126.84 (capital + interes al vencimiento)

3. FRENCH_FIXED_PAYMENT — sistema frances

r = simulate(
    mode=AmortizationMode.FRENCH_FIXED_PAYMENT,
    principal="15000.00", annual_rate="0.24", periods=12,
    start_date="2026-08-09",
)
print(r.schedule[0].payment)     # 1418.39 (cuota fija)
print(r.summary.total_interest)  # 2020.68
print(r.summary.total_paid)      # 17020.68

4. GERMAN_CONSTANT_AMORTIZATION — sistema aleman

r = simulate(
    mode=AmortizationMode.GERMAN_CONSTANT_AMORTIZATION,
    principal="15000.00", annual_rate="0.24", periods=12,
    start_date="2026-08-09",
)
print(r.schedule[0].interest_component)   # 300.00 (decrece linealmente)
print(r.schedule[-1].interest_component)  # 25.00
print(r.summary.total_interest)           # 1950.00
print(r.summary.total_paid)               # 16950.00

5. AMERICAN_BULLET — sistema americano

r = simulate(
    mode=AmortizationMode.AMERICAN_BULLET,
    principal="10000.00", annual_rate="0.12", periods=12,
    start_date="2026-08-09",
)
print(r.schedule[0].payment)    # 100.00 (solo interes)
print(r.schedule[-1].payment)   # 10100.00 (capital + interes final)
print(r.summary.total_interest) # 1200.00
print(r.summary.total_paid)     # 11200.00

6. DOWN_PAYMENT_ADJUSTED — con enganche

El enganche se paga fuera de la tabla; el motor amortiza con sistema frances el capital efectivo principal - down_payment.

r = simulate(
    mode=AmortizationMode.DOWN_PAYMENT_ADJUSTED,
    principal="15000.00", annual_rate="0.24", periods=12,
    down_payment="3000.00", start_date="2026-08-09",
)
print(r.schedule[0].principal_component)   # 894.72
print(r.schedule[0].balance)               # 11105.28
print(r.summary.total_interest)            # 1616.64
print(sum(x.principal_component for x in r.schedule))  # 12000.00 (cuadre exacto)

7. EXTRA_PAYMENTS — pagos extraordinarios

Dos submodalidades via extra_payment_behavior. Abono de 2 000 en el periodo 6 sobre el credito frances de 15 000:

r = simulate(
    mode=AmortizationMode.EXTRA_PAYMENTS,
    principal="15000.00", annual_rate="0.24", periods=12,
    extra_payments=[{"period": 6, "amount": "2000.00"}],
    extra_payment_behavior="reduce_term",   # misma cuota, plazo mas corto
    start_date="2026-08-09",
)
print(r.schedule[5].extra_payment)   # 2000.00
print(r.schedule[5].balance)         # 5945.07
print(r.summary.periods_count)       # 11 (el plazo se acorto)
print(r.summary.total_interest)      # 1784.76

Y con reduce_payment (mismo plazo, cuota menor):

r = simulate(
    mode=AmortizationMode.EXTRA_PAYMENTS,
    principal="15000.00", annual_rate="0.24", periods=12,
    extra_payments=[{"period": 6, "amount": "2000.00"}],
    extra_payment_behavior="reduce_payment",
    start_date="2026-08-09",
)
print(r.schedule[0].principal_component)  # 950.46
print(r.summary.total_interest)           # 2005.54
print(r.summary.total_paid)               # 17005.54

8. ZERO_INTEREST — sin intereses

La tasa anual debe ser exactamente 0.00; cada cuota es principal / n.

r = simulate(
    mode=AmortizationMode.ZERO_INTEREST,
    principal="6000.00", annual_rate="0.00", periods=12,
    start_date="2026-08-09",
)
print(r.schedule[0].payment)      # 500.00
print(r.summary.total_interest)   # 0.00
print(r.summary.total_paid)       # 6000.00

9. VARIABLE_RATE — tasa variable

Base de amortizacion constante; rate_schedule cambia la tasa del periodo 7 en adelante al 30 % anual (la tasa vigente se expone en row.extra_data["annual_rate"]):

r = simulate(
    mode=AmortizationMode.VARIABLE_RATE,
    principal="15000.00", annual_rate="0.24", periods=12,
    rate_schedule=[{"period_from": 7, "period_to": None, "annual_rate": "0.30"}],
    start_date="2026-08-09",
)
print(r.schedule[0].interest_component)    # 300.00 (24 % anual)
print(r.schedule[6].interest_component)    # 187.50 (30 % anual)
print(r.summary.total_interest)            # 2081.25
print(r.summary.total_paid)                # 17081.25

10. CUSTOM_SCHEDULE — pagos personalizados

El usuario define el monto de cada pago (custom_payments); el motor distribuye cada pago entre interes y capital. Solo se emiten filas para los periodos pagados.

r = simulate(
    mode=AmortizationMode.CUSTOM_SCHEDULE,
    principal="10000.00", annual_rate="0.12", periods=12,
    custom_payments=[
        {"period": 1, "amount": "1100.00"},
        {"period": 6, "amount": "2100.00"},
        {"period": 12, "amount": "7000.00"},
    ],
    start_date="2026-08-09",
)
print(r.schedule[0].principal_component)  # 1000.00
print(r.schedule[-1].payment)             # 7059.90 (ultima fila cuadrada)
print(r.summary.total_interest)           # 259.90
print(r.summary.total_paid)               # 10259.90

11. GRACE_PERIOD_DEFERRED — periodo de gracia

Submodalidad interest_only (se paga solo interes durante la gracia):

r = simulate(
    mode=AmortizationMode.GRACE_PERIOD_DEFERRED,
    principal="15000.00", annual_rate="0.24", periods=12,
    grace_periods=3, grace_period_behavior="interest_only",
    start_date="2026-08-09",
)
print(r.schedule[0].interest_component)  # 300.00
print(r.summary.total_interest)          # 2439.57
print(r.summary.total_paid)              # 17439.57

Submodalidad deferred (sin pagos, el interes se capitaliza en el saldo):

r = simulate(
    mode=AmortizationMode.GRACE_PERIOD_DEFERRED,
    principal="15000.00", annual_rate="0.24", periods=12,
    grace_periods=3, grace_period_behavior="deferred",
    start_date="2026-08-09",
)
print(r.metadata["principal_effective"])     # 15918.12 (saldo capitalizado)
print(r.metadata["capitalized_interest"])    # 918.12
print(r.summary.total_interest)              # 2551.98
print(r.summary.total_paid)                  # 17551.98

12. FEES_AND_INSURANCE — con cargos/seguro

Base francesa mas un cargo porcentual sobre saldo (seguro, 2 % anual, cobrado mensualmente) en fees_component:

r = simulate(
    mode=AmortizationMode.FEES_AND_INSURANCE,
    principal="15000.00", annual_rate="0.24", periods=12,
    fees=[{"name": "seguro", "rate": "0.02"}],
    start_date="2026-08-09",
)
print(r.schedule[0].fees_component)   # 25.00 (decrece con el saldo)
print(r.schedule[-1].fees_component)  # 2.32
print(r.summary.total_fees)           # 168.40
print(r.summary.total_paid)           # 17189.08

13. INDEXED_INFLATION — indexado a inflacion

El saldo se ajusta por el factor compuesto del indice (un factor por periodo) y el capital efectivo se amortiza en sistema frances:

r = simulate(
    mode=AmortizationMode.INDEXED_INFLATION,
    principal="1000.00", annual_rate="0.12", periods=12,
    inflation_index=["1.004"] * 12,
    start_date="2026-08-09",
)
print(r.metadata["principal_effective"])  # 1049.07
print(r.summary.total_interest)           # 69.45
print(r.summary.total_paid)               # 1118.52

Principios de diseno

  • Precision decimal exacta: todo monto y tasa se calcula con decimal.Decimal, nunca float. El redondeo es explicito y configurable (rounding="ROUND_HALF_UP" por defecto) y el cuadre de capital esta garantizado: sum(principal_component) == principal y saldo final en 0.00.
  • Sin estado (stateless): funciones puras y dataclasses(frozen=True) para entradas y salidas. Cada calculo recibe todo como parametro y devuelve un resultado inmutable; no hay persistencia ni efectos secundarios.
  • Determinista: las fechas siempre llegan como parametro (date o str ISO), nunca datetime.now() implicito. Misma entrada, mismo resultado exacto.
  • Inmutabilidad matematica: una vez publicada, una modalidad no se modifica. Si hay que cambiar una formula se crea una nueva modalidad (p.ej. ..._V2), nunca se altera la existente.
  • Schema de salida fijo: AmortizationResult y PaymentRow exponen siempre los mismos campos en las 13 modalidades; los montos no aplicables se emiten como 0.00 y los datos especificos de modalidad viajan en extra_data / metadata.
  • Extensible: cada modalidad es una estrategia de la interfaz AmortizationStrategy; anadir la modalidad 14 no rompe nada existente.
  • Serializable: to_dict() y to_json() en todos los resultados, sin dependencias externas (los Decimal se serializan como str para un roundtrip JSON exacto; las fechas como ISO 8601).
  • Errores propios: jerarquia FinancialEngineError con subclases accionables; validacion agresiva, nunca assert.

API publica

simulate(mode, request=None, **kwargs) -> AmortizationResult

Unica funcion de entrada de la libreria. Resuelve la modalidad, construye la AmortizationRequest, ejecuta la estrategia y devuelve el resultado inmutable.

  • Semantica request XOR kwargs: se pasa un AmortizationRequest preconstruido o campos sueltos en **kwargs, nunca ambos (pasar ambos lanza InvalidParametersError). mode es obligatorio en ambos casos.
  • Conversion de tipos amigable en **kwargs: montos y tasas como str/int/Decimal (el float se rechaza explicitamente); fechas ISO como str; listas de ExtraPayment/RateChange/Fee/CustomPayment como dataclasses o dicts planos; annual_rate es obligatorio.
  • Los campos opcionales con valor por defecto son periodicity="monthly" y rounding="ROUND_HALF_UP".

AmortizationMode

Enum con los 13 miembros listados en la tabla anterior. Tambien acepta el nombre como str (el valor del enum es identico al nombre, p.ej. "FRENCH_FIXED_PAYMENT").

AmortizationRequest

Dataclass frozen=True con los campos de entrada (los opcionales son None cuando no aplican):

  • principal: Decimal — monto del credito (obligatorio, no negativo).
  • annual_rate: Decimal — tasa anual como fraccion, p.ej. 0.24 = 24 % (obligatorio en todas las modalidades; ZERO_INTEREST exige 0.00).
  • periods: int — numero de periodos (>= 1).
  • periodicity: Literal["monthly","biweekly","weekly","annual"].
  • start_date: date — base del calendario (el periodo 1 cae en start_date + un periodo).
  • rounding: str = "ROUND_HALF_UP" — modo de redondeo de decimal.
  • down_payment: Decimal | None — enganche descontado del principal.
  • extra_payments: list[ExtraPayment] | None — abonos a capital.
  • rate_schedule: list[RateChange] | None — cambios de tasa por rango.
  • fees: list[Fee] | None — cargos fijos o porcentuales.
  • grace_periods: int | None — periodos iniciales con diferimiento.
  • inflation_index: list[Decimal] | None — factores de ajuste por periodo.
  • extra_payment_behavior: Literal["reduce_term","reduce_payment"] = "reduce_term".
  • grace_period_behavior: Literal["deferred","interest_only"] = "deferred".
  • custom_payments: list[CustomPayment] | None — pagos manuales por periodo.

Dataclasses auxiliares (todas frozen=True): ExtraPayment(period, amount), RateChange(period_from, period_to, annual_rate), Fee(name, amount=None, rate=None, periodicity="monthly") y CustomPayment(period, amount).

Resultado: AmortizationResult

  • mode: AmortizationMode — modalidad aplicada.
  • schedule: list[PaymentRow] — calendario de pagos.
  • summary: AmortizationSummary — totales agregados.
  • metadata: dict — parametros originales del calculo (trazabilidad) y datos de contexto de la modalidad.
  • to_dict() -> dict y to_json() -> str.

PaymentRow (schema fijo): period, date, payment, principal_component, interest_component, fees_component, balance, extra_payment y extra_data (dict opcional con datos especificos de la modalidad, p.ej. index_factor, capitalized_interest).

AmortizationSummary: total_paid, total_interest, total_fees, effective_rate (tasa periodica) y periods_count; con to_dict() / to_json().

Excepciones

Jerarquia propia que hereda de FinancialEngineError:

  • InvalidParametersError — parametros que violan el contrato de entrada (periodos < 1, calendarios inconsistentes, request + kwargs a la vez, float en un monto, annual_rate=None, etc.).
  • NegativePrincipalError — principal negativo.
  • RateOutOfRangeError — tasa anual fuera de [0, 1] (inclusive).
  • ScheduleMismatchError — el calendario no cuadra con la solicitud.

Captura la raiz except FinancialEngineError o la subclase concreta segun lo que necesites.

Desarrollo

Clonar el repo, crear el venv e instalar en modo editable:

git clone https://github.com/jesusmrv81/amortizacion-engine.git
cd amortizacion-engine
python3.12 -m venv .venv
.venv/bin/pip install -e .
.venv/bin/pip install -r requirements-dev.txt

Ejecutar la suite completa con cobertura (objetivo >= 90 %):

.venv/bin/pytest --cov=financial_engine --cov-report=term-missing

Lint y formato:

.venv/bin/ruff check .
.venv/bin/black --check .

Los docstrings del API publico incluyen ejemplos verificables; se pueden ejecutar como doctests con pytest:

.venv/bin/pytest --doctest-modules src/financial_engine/ -q

Contributing

Las contribuciones son bienvenidas:

  1. Haz un fork del repositorio y crea una rama para tu cambio.
  2. Anade o actualiza los tests (pytest) y verifica que la cobertura se mantiene en el 100 %.
  3. Ejecuta ruff check . y black . antes de abrir el PR.
  4. Respeto a la inmutabilidad matematica: no modifiques la formula de una modalidad ya publicada. Si necesitas cambiar una formula, crea una modalidad nueva (p.ej. FRENCH_FIXED_PAYMENT_V2) y registrala en AmortizationMode.

FAQ

¿Por que Decimal y no float?

El dinero no admite errores de punto flotante. decimal.Decimal representa montos y tasas de forma exacta y permite un control explicito del redondeo (ROUND_HALF_UP por defecto), lo que garantiza que dos calculos identicos produzcan siempre el mismo resultado exacto y que el cuadre del capital cierre al centavo.

¿Como garantiza el cuadre del capital?

El motor concilia la ultima fila de cada calendario (reconcile_last_payment): ajusta el ultimo pago para que sum(principal_component) sea exactamente igual al principal original y el saldo final quede en 0.00, sin arrastrar redondeos intermedios.

¿Puedo usar fechas reales?

Si. La fecha base del calendario se pasa como parametro (start_date, como datetime.date o str ISO "YYYY-MM-DD") y el motor calcula las fechas de cada periodo a partir de ella. El motor es determinista: nunca usa datetime.now() implicito.

¿Es libre?

Si, esta bajo licencia MIT. Puedes usarlo, modificarlo y distribuirlo en proyectos comerciales o personales sin restricciones (ver LICENSE).

¿Como anado una modalidad nueva?

Implementa una clase que herede de AmortizationStrategy con su metodo compute(), registrala en el mapa de estrategias y anade el miembro correspondiente a AmortizationMode. El resto de la API (validacion, schema de salida, serializacion) funciona sin cambios. Nunca modifiques una modalidad ya publicada: crea una _V2.

Licencia

MIT — ver LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

amortizacion_engine-0.1.0.tar.gz (74.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

amortizacion_engine-0.1.0-py3-none-any.whl (63.8 kB view details)

Uploaded Python 3

File details

Details for the file amortizacion_engine-0.1.0.tar.gz.

File metadata

  • Download URL: amortizacion_engine-0.1.0.tar.gz
  • Upload date:
  • Size: 74.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for amortizacion_engine-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b76a56f10f030896a182d01ca924276fa5fdc3ded580d048c67f5f1860c529dc
MD5 22e744d455086468275ade86fe31e1df
BLAKE2b-256 7bebe6082046095f70e50950bb012a9c3695b5d8e539519c97501f86ec10488a

See more details on using hashes here.

Provenance

The following attestation bundles were made for amortizacion_engine-0.1.0.tar.gz:

Publisher: publish.yml on jesusmrv81/amortizacion-engine

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file amortizacion_engine-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for amortizacion_engine-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 16e71eba170b4f8ad1681f177d32d159c83bb49647fd1539100c1455bf6373aa
MD5 e92d161ddb9d2387e81b9c4b9f3cb679
BLAKE2b-256 73b3db7b1f10f38e22b2cc6f8c41bc7e7b302b657c6980edc652124665c9832a

See more details on using hashes here.

Provenance

The following attestation bundles were made for amortizacion_engine-0.1.0-py3-none-any.whl:

Publisher: publish.yml on jesusmrv81/amortizacion-engine

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page