Created
June 21, 2023 17:26
-
-
Save 0xpatrickdev/dbefd7b6829f067449810711d062a2d7 to your computer and use it in GitHub Desktop.
interest math agoric
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @ts-check | |
import { | |
makeRatio, | |
multiplyBy, | |
multiplyRatios | |
} from '@agoric/zoe/src/contractSupport/ratio.js'; | |
import '@agoric/ertp/src/types-ambient.js'; | |
export const MS_IN_A_YEAR = 31_557_600_000n; | |
const BPS_IN_DECIMAL = 10_000n; | |
/** | |
* @param {bigint} decimals | |
* @example | |
* // returns 10_000n | |
* scale(4n) | |
* @returns {bigint} | |
*/ | |
export const scale = decimals => 10n ** decimals; | |
/** | |
* Simple interest - principal * rate (annual) * time (annual) | |
* @param {Amount<"nat">} principal | |
* @param {bigint} rateBps annual interest rate in basis points | |
* @param {bigint} timeMs time elapsed in ms | |
* @param {Brand} brand | |
* @returns {Amount<"nat">} | |
*/ | |
export const calculateSimpleInterest = (principal, rateBps, timeMs, brand) => { | |
// convert bps to decimal ratio | |
const rate = makeRatio(rateBps, brand, BPS_IN_DECIMAL); | |
// convert msElapsed to yearly ratio | |
const time = makeRatio(timeMs, brand, MS_IN_A_YEAR); | |
// principal * rate * time | |
return multiplyBy(principal, multiplyRatios(rate, time)); | |
}; | |
/** | |
* Continuous interest - principal * e ^ (rate * time) | |
* @param {Amount<"nat">} principal | |
* @param {bigint} rateBps annual interest rate in basis points | |
* @param {bigint} timeMs time elapsed in ms | |
* @param {Brand} brand | |
* @param {bigint} decimals the number of precision decimals. default is 6n | |
* @returns {Amount<"nat">} | |
*/ | |
export const calculateContinuousInterest = ( | |
principal, | |
rateBps, | |
timeMs, | |
brand, | |
decimals = 6n | |
) => { | |
const time = Number(timeMs) / Number(MS_IN_A_YEAR); | |
const rate = Number(rateBps) / Number(BPS_IN_DECIMAL); | |
const cumulativeRate = Math.exp(time * rate) - 1; | |
const denominator = scale(decimals); | |
const scaledRate = BigInt(Math.round(cumulativeRate * Number(denominator))); | |
return multiplyBy(principal, makeRatio(scaledRate, brand, denominator)); | |
}; | |
/** | |
* Returns Euler's Constant, e, as a Ratio (≈2.7182) | |
* | |
* @param {Brand} brand the ERTP brand we should use for the ratio | |
* @param {bigint} decimals the number of precision decimals. default is 4n | |
* @returns {Ratio} | |
*/ | |
export const makeEulerConstantRatio = (brand, decimals = 4n) => { | |
const denominator = scale(decimals); | |
const eulerConstant = BigInt(Math.round(Math.E * Number(denominator))); | |
return makeRatio(eulerConstant, brand, denominator); | |
}; | |
/** | |
* @todo this does not work. It does a * b instead of a ** b. Can this be built | |
* in contractSupport/ratio.js? | |
* @param {Ratio} base | |
* @param {Ratio} exponent | |
* @returns {Ratio} | |
*/ | |
const exponentiateRatios = (base, exponent) => multiplyRatios(base, exponent); | |
/** | |
* Continuous interest - principal * e ^ (rate * time) | |
* @todo doesn't work without support for exponents/logarithms in contractSupport/ratio.js | |
* @param {Amount<"nat">} principal | |
* @param {bigint} rateBps annual interest rate in basis points | |
* @param {bigint} timeMs time elapsed in ms | |
* @param {Brand} brand | |
* @param {bigint} decimals the number of precision decimals. default is 6n | |
* @returns {Amount<"nat">} | |
*/ | |
export const calculateContinuousInterestWithRatios = ( | |
principal, | |
rateBps, | |
timeMs, | |
brand, | |
decimals = 6n | |
) => { | |
// convert bps to decimal ratio | |
const rate = makeRatio(rateBps, brand, 10_000n); | |
// convert msElapsed to yearly ratio | |
const time = makeRatio(timeMs, brand, MS_IN_A_YEAR); | |
// 2.7182 as a ratio | |
const eulerConstantRatio = makeEulerConstantRatio(brand, decimals); | |
return multiplyBy( | |
principal, | |
exponentiateRatios(eulerConstantRatio, multiplyRatios(rate, time)) | |
); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @ts-check | |
import { test } from '../prepare-test-env-ava.js'; | |
import { AmountMath, makeIssuerKit } from '@agoric/ertp'; | |
import { | |
calculateSimpleInterest, | |
calculateContinuousInterest, | |
makeEulerConstantRatio, | |
scale, | |
MS_IN_A_YEAR | |
} from './interest.js'; | |
const { brand } = makeIssuerKit('foo'); | |
test('utils/interest scale', t => { | |
t.is(scale(1n), 10n); | |
t.is(scale(2n), 100n); | |
t.is(scale(3n), 1_000n); | |
t.is(scale(4n), 10_000n); | |
t.is(scale(9n), 1_000_000_000n); | |
t.is(Number(scale(3n)), 1000); | |
}); | |
test('utils/interest calculateSimpleInterest', t => { | |
const principal = AmountMath.make(brand, 1_000n); // $1,000 | |
const rateBps = 400n; // 4% | |
const timeMs = MS_IN_A_YEAR / 2n; // 6 months | |
const interest = calculateSimpleInterest(principal, rateBps, timeMs, brand); | |
t.is(interest.value, 20n); | |
t.is( | |
calculateSimpleInterest( | |
AmountMath.make(brand, 10_000n), | |
400n, | |
MS_IN_A_YEAR / 2n, | |
brand | |
).value, | |
200n | |
); | |
t.is( | |
calculateSimpleInterest( | |
AmountMath.make(brand, 500_000n), | |
50n, | |
MS_IN_A_YEAR / 4n, | |
brand | |
).value, | |
625n | |
); | |
}); | |
test('utils/interest calculateContinuousInterest', t => { | |
const principal = AmountMath.make(brand, 1_000n); // $1,000 | |
const rateBps = 400n; // 4% | |
const timeMs = MS_IN_A_YEAR / 2n; // 6 months | |
const interest = calculateContinuousInterest( | |
principal, | |
rateBps, | |
timeMs, | |
brand | |
); | |
t.is(interest.value, 20n); | |
t.is( | |
calculateContinuousInterest( | |
AmountMath.make(brand, 10_000n), | |
400n, | |
MS_IN_A_YEAR / 2n, | |
brand | |
).value, | |
202n | |
); | |
t.is( | |
calculateContinuousInterest( | |
AmountMath.make(brand, 500_000n), | |
50n, | |
MS_IN_A_YEAR / 4n, | |
brand | |
).value, | |
626n | |
); | |
t.is( | |
calculateContinuousInterest( | |
AmountMath.make(brand, 1_000_000n), | |
50n, | |
MS_IN_A_YEAR / 4n, | |
brand | |
).value, | |
1251n, | |
'default precision of 6' | |
); | |
t.is( | |
calculateContinuousInterest( | |
AmountMath.make(brand, 1_000_000n), | |
50n, | |
MS_IN_A_YEAR / 4n, | |
brand, | |
4n | |
).value, | |
1300n, | |
'precision of only 4 decimals' | |
); | |
t.is( | |
calculateContinuousInterest( | |
AmountMath.make(brand, 1_000_000n), | |
50n, | |
MS_IN_A_YEAR / 4n, | |
brand, | |
8n | |
).value, | |
1251n, | |
'custom precision of 8 decimals' | |
); | |
}); | |
test('utils/interest makeEulerConstantRatio, base scale', t => { | |
const eulerConstantRatio = makeEulerConstantRatio(brand); | |
t.is(eulerConstantRatio.numerator.value, 27_183n); | |
t.is(eulerConstantRatio.denominator.value, 10_000n); | |
const eulerConstantRatioAsDecimal = | |
Number(eulerConstantRatio.numerator.value) / | |
Number(eulerConstantRatio.denominator.value); | |
t.is(eulerConstantRatioAsDecimal, 2.7183); | |
}); | |
test('utils/interest makeEulerConstantRatio, custom scale', t => { | |
const eulerConstantRatio = makeEulerConstantRatio(brand, 6n); | |
t.is(eulerConstantRatio.numerator.value, 2_718_282n); | |
t.is(eulerConstantRatio.denominator.value, 1_000_000n); | |
const eulerConstantRatioAsDecimal = | |
Number(eulerConstantRatio.numerator.value) / | |
Number(eulerConstantRatio.denominator.value); | |
t.is(eulerConstantRatioAsDecimal, 2.718282); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment