Skip to content

Commit

Permalink
✨ installments: calculate effective rate
Browse files Browse the repository at this point in the history
  • Loading branch information
cruzdanilo committed Apr 12, 2024
1 parent 71abd70 commit a1f8ef4
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 2 deletions.
21 changes: 20 additions & 1 deletion src/installments/split.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import expWad from "../fixed-point-math/expWad.js";
import lnWad from "../fixed-point-math/lnWad.js";
import fixedRate, { INTERVAL, type IRMParameters } from "../interest-rate-model/fixedRate.js";
import abs from "../vector/abs.js";
import add from "../vector/add.js";
import fill from "../vector/fill.js";
import mean from "../vector/mean.js";
import mulDivUp from "../vector/mulDivUp.js";
import powDiv from "../vector/powDiv.js";
import sub from "../vector/sub.js";
import sum from "../vector/sum.js";

Expand All @@ -23,6 +25,7 @@ export default function splitInstallments(
power = (WAD * 60n) / 100n,
scaleFactor = (WAD * 95n) / 100n,
tolerance = WAD / 1_000_000_000n,
rateTolerance = 10_000n,
maxIterations = 66_666n,
} = {},
) {
Expand Down Expand Up @@ -59,7 +62,23 @@ export default function splitInstallments(
error = mean(mulDivUp(abs(diffs), weight, WAD));
} while (error >= tolerance);

return { amounts, installments, rates };
const maturityFactors = rates.map(
(_, index) =>
(BigInt(firstMaturity + index * INTERVAL - timestamp) * WAD) /
BigInt(timestamp + maxPools * INTERVAL - (timestamp % INTERVAL)),
);
let effectiveRate = rates[0]!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
error = 0n;
do {
const aux = add(mulDivUp(maturityFactors, effectiveRate, WAD), WAD);
const f = sum(mulDivUp(installments, WAD, aux)) - totalAmount;
const fp = -sum(mulDivUp(mulDivUp(installments, maturityFactors, WAD), WAD, powDiv(aux, 2n, WAD)));
const rateDiff = (-f * WAD) / fp;
effectiveRate += rateDiff;
error = rateDiff < 0n ? -rateDiff : rateDiff;
} while (error >= rateTolerance);

return { amounts, installments, rates, effectiveRate };
}

function max(a: bigint, b: bigint) {
Expand Down
5 changes: 5 additions & 0 deletions src/vector/add.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import map2 from "./map2.js";

export default function add(a: readonly bigint[], b: readonly bigint[] | bigint) {
return map2(a, b, (a_, b_) => a_ + b_);
}
3 changes: 3 additions & 0 deletions src/vector/max.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function max(array: readonly bigint[]) {
return array.reduce((maxValue, value) => (value > maxValue ? value : maxValue)); // eslint-disable-line unicorn/no-array-reduce
}
3 changes: 3 additions & 0 deletions src/vector/min.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function min(array: readonly bigint[]) {
return array.reduce((minValue, value) => (value < minValue ? value : minValue)); // eslint-disable-line unicorn/no-array-reduce
}
5 changes: 5 additions & 0 deletions src/vector/powDiv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import map3 from "./map3.js";

export default function powDiv(a: readonly bigint[], b: readonly bigint[] | bigint, c: readonly bigint[] | bigint) {
return map3(a, b, c, (a_, b_, c_) => a_ ** b_ / c_);
}
6 changes: 5 additions & 1 deletion test/installments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { parseUnits } from "viem";

import WAD, { SQ_WAD } from "../src/fixed-point-math/WAD";
import { INTERVAL, type IRMParameters } from "../src/interest-rate-model/fixedRate";
import max from "../src/vector/max";
import mean from "../src/vector/mean";
import min from "../src/vector/min";
import mulDivDown from "../src/vector/mulDivDown";
import sum from "../src/vector/sum";

Expand All @@ -28,7 +30,7 @@ describe("installments", () => {
totalAmount = (totalAmount * totalAssets * (WAD - uGlobal)) / SQ_WAD;
if (sum(uFixed) > 0n) uFixed = mulDivDown(uFixed, uGlobal - uFloating, sum(uFixed));

const { amounts, installments } = splitInstallments(
const { amounts, installments, rates, effectiveRate } = splitInstallments(
totalAmount,
totalAssets,
firstMaturity,
Expand All @@ -43,6 +45,8 @@ describe("installments", () => {
expect(amounts).toHaveLength(uFixed.length);
expect(sum(amounts)).toBeGreaterThanOrEqual(totalAmount);
expect(sum(amounts) - totalAmount).toBeLessThan(totalAmount / 100_000n);
expect(effectiveRate).toBeGreaterThanOrEqual(min(rates));
expect(effectiveRate).toBeLessThanOrEqual(max(rates));

const avg = mean(installments);
for (const installment of installments) {
Expand Down

0 comments on commit a1f8ef4

Please sign in to comment.