Money, read from a lender's source
This essay is not financial advice. It describes how offers of credit can be compared and arithmetic with published formulas, and it reads a program that states them. The cheapest offer in total and an offer over a household's limit can be the same offer, and the program reports both and does not say which to take. Every rate, price and income in it is an example. Nothing here is a recommendation for anyone's money. Ask a financial adviser.
The records of interest are very old. The article on interest says that the first recorded instance of credit is a collection of old Sumerian documents from 3000 BC, and that the first written evidence of compound interest dates roughly from 2400 BC, with an annual interest rate of roughly 20%. A clay tablet from Babylon of about 2000 to 1700 BC might be the first display of the compound interest problem. The charge itself was a moral question for a very long time: the First Council of Nicaea, in 325, forbade clergy from engaging in usury, which it defined as lending on interest above 1 percent per month, and Thomas Aquinas argued that charging interest was wrong because it amounted to double charging. The article on compound interest says that compound interest charged by lenders was once regarded as the worst kind of usury. The arithmetic that came out of that long argument is what a borrower still meets: a payment, a total, a rate that is not quite what it says, and a number of years.
The finance library for DKE, the Deterministic Knowledge Engine, writes down some of that arithmetic in DKE Python, and this essay teaches the subject by reading it. It is two halves that share nothing but the subject. The first records loan offers and household limits as facts and concludes which offer costs the least in total and which carry a payment over a limit. The second calculates: the amortised payment, compound interest, the effective annual rate of a nominal one and of continuous compounding, the doubling time by the exact form and by the rule of 72, present value, a value in today's money and the real rate. Every program below runs as shown once the library's source file has been compiled into a store, and every output is copied from a run. The programs name their own subjects and print only those, so that a store which already holds other offers gives the same lines.
1. The payment of a loan
A loan repaid in equal monthly payments is an annuity, and the payment that retires it has a closed form. The article on the amortization calculator gives the payment as A = P i / (1 - (1 + i)^-n), where P is the principal, i the periodic interest rate and n the total number of payments, valid if i is greater than zero, and "if i = 0 then simply A = P/n". For a rate stated as an annual percentage rate, dividing by 12 is an appropriate means of finding the monthly rate. The library writes it so, with the rate a fraction, 0.05 for five percent:
i = annual_rate / 12.0
d = -math.expm1(-months * math.log1p(i))
v = principal * i / dThe d is the formula's 1 - (1 + i)^-n, written so that it keeps its digits: (1 + i)^-n is close to 1 over a short loan or at a low rate, and subtracting two nearly equal numbers loses the digits they share, so the library takes the power as e to the n times the logarithm of 1 + i, and the subtraction from 1 inside the same function, expm1, which never forms the number near 1.
The article on compound interest works a mortgage: a loan of $120,000 over 30 years at 4.5%, paid monthly, has an exact payment of $608.02. The program asks the library for that payment and for two more, a loan of $200,000 at 5% over 360 months and one of the same size at no interest over 240:
import finance
print(finance.monthly_payment(120000.0, 0.045, 360.0))
print(finance.monthly_payment(200000.0, 0.05, 360.0), finance.monthly_payment(200000.0, 0.0, 240.0))608.0223717910568
1073.6432460242781 833.3333333333334The first is 608.0223717910568, and the page's $608.02 is that to the cent. The library does not round to the cent. A bank rounds each payment to the cent and builds a schedule from the rounded payments, and this library gives a payment as the real number of currency units the formula yields, and a total as that payment times the months. It is for comparing, not for reproducing a lender's statement. Zero months refuses with a division by zero, since there is no payment that retires a loan in no time, and a rate of zero is not a refusal: the payment is the principal over the months.
2. Which offer costs the least
A household does not meet a formula. It meets offers: a payment and a number of months, from a lender, for a sum. The library takes an offer as a fact, with the borrower it is for, the principal, the monthly payment as the lender states it and the number of months, and the offer's total and interest are worked out from them as fields:
total: real = payment * months
interest: real = (0.5 * payment * months - 0.5 * principal) * 2.0The rule that concludes the cheapest offer is the interesting one, because of what it does not say. It does not say that an offer is cheaper than some other offer, which would mark an offer that a third one beats. It says that an offer is the cheapest among those that share its borrower and its principal when there is no offer in that group with a smaller total:
@rule
def cheapest_offer():
for a in Offer:
if none(b in Offer: b.borrower == a.borrower and b.principal == a.principal and b.total < a.total):
a.cheapest = TrueIt holds while no cheaper offer is present, and it is withdrawn when one is written. A second rule marks an offer whose payment is over the limit of the budget that is named by its borrower. A budget is a limit on the monthly payment that a household can bear. The practice of lenders gives such limits a name: the article on the debt-to-income ratio says that it is the percentage of a consumer's monthly gross income that goes toward paying debts, and that the two main kinds are expressed as a pair x/y, for example 28/36, the first the share of income that goes to housing costs and the second the share that goes to all recurring debt payments. At 28/36 on an income of $45,000 a year, which is $3,750 a month, a lender allows $1,050 for housing and $1,350 for housing plus recurring debt. FHA limits are currently 31/43, and Fannie Mae generally permits a total of up to 36 percent for manually underwritten loans, or up to 45 percent when its credit-score and reserve requirements are met. The limits vary and are the lender's, and the library takes yours as a number.
Kari has been quoted three loans of $200,000: at 5% over 360 months, at 5.5% over 240 and at 6% over 120. Her income is $64,285.71 a year, so that 28% of its twelfth is a budget of $1,500 a month, and she gives it to the library:
import finance
ours = ["slow", "mid", "quick"]
finance.quote("slow", "kari", 200000.0, 0.05, 360.0)
finance.quote("mid", "kari", 200000.0, 0.055, 240.0)
finance.quote("quick", "kari", 200000.0, 0.06, 120.0)
finance.budget("kari", 1500.0)
print([n for n in finance.cheapest_offers() if n in ours], [n for n in finance.unaffordable_offers() if n in ours])
for n in ours:
print(n, finance.total(n), finance.total(n) - 200000.0)
w = why_not(finance.Offer.slow.cheapest)
print(w)
x = why_not(finance.Offer.mid.unaffordable)
print(x)['quick'] ['quick']
slow 386511.56856874016 186511.56856874016
mid 330185.9077724346 130185.90777243458
quick 266449.20465995866 66449.20465995866
rule `finance.cheapest_offer` needs finance.Offer[slow].cheapest_offer__none1 to be absent, and it is present
rule `finance.unaffordable_offer` needs finance.Offer[mid].payment > finance.Budget[kari].limit, and finance.Offer[mid].payment is 1375.7746157184774 (real)The quickest loan is the cheapest in total, by a wide margin: it costs 66,449.20 beyond the sum lent, and the slowest costs 186,511.57. It is also the only one over the household's limit, with a payment of 2,220.41, and the library marks it unaffordable. Both are true of the same offer, and the library says both and says nothing of which to take. That is the right division of labour: the cheapest loan is a fact about totals, the limit is a fact about a household, and a person who has to choose has to weigh them. The why_not of the slow loan's cheapest names the helper that the rule makes for its absence test, and says that the cheaper offer, the quick one, is present. The why_not of the middle loan's payment names its payment, 1,375.77, and the budget it is not over.
3. Equal totals, the edge and a household with no limit
Three smaller cases are the rule's fine print. Two offers with equal totals are each the cheapest, because neither has a cheaper one, and the library ranks nothing. A payment exactly at the limit is not over it, as a dose at exactly one is not over its limit in the library on sound. And a borrower with no budget has no offer marked unaffordable, because nothing is recorded to be over:
import finance
ours = ["twin1", "twin2", "dear", "edge", "nobud"]
finance.offer("twin1", "ola", 10000.0, 100.0, 120.0)
finance.offer("twin2", "ola", 10000.0, 200.0, 60.0)
finance.offer("dear", "ola", 10000.0, 150.0, 120.0)
print([n for n in finance.cheapest_offers() if n in ours])
finance.budget("per", 100.0)
finance.offer("edge", "per", 5000.0, 100.0, 60.0)
finance.offer("nobud", "zed", 5000.0, 900.0, 60.0)
print([n for n in finance.unaffordable_offers() if n in ours])
finance.offer("edge", "per", 5000.0, 100.01, 60.0)
print([n for n in finance.unaffordable_offers() if n in ours])['twin1', 'twin2']
[]
['edge']The twins are loans of $10,000 whose totals are both 12,000, one over ten years and one over five, and both are cheapest; the third, which costs 18,000, is not. The library will not say that the shorter is better. An edge of 100.00 against a limit of 100.00 is not over it and 100.01 is. And the offer of 900 a month to a borrower with no budget is in no list.
4. When two lenders disagree
Two sources can say different things about one offer: the lender's quotation and the broker's, or a figure typed twice. The library keeps both and chooses neither. A reading is written under a second source, beside the first. unsettled() names every fact you write that two sources give different values, and the readers of names read every value in force. The single number that total returns is read with current(), which refuses a cell that holds two values rather than choose one.
import finance
ours = ["hom1", "hom2"]
finance.quote("hom1", "dina", 100000.0, 0.04, 180.0)
finance.quote("hom2", "dina", 100000.0, 0.05, 180.0)
finance.budget("dina", 800.0)
print([n for n in finance.cheapest_offers() if n in ours], [n for n in finance.unaffordable_offers() if n in ours])
print([s for s in finance.unsettled() if s.split(" ")[1] in ours])
update(finance.Offer.hom1.payment, 900.0, "lender B")
print([n for n in finance.cheapest_offers() if n in ours], [n for n in finance.unaffordable_offers() if n in ours])
print([s for s in finance.unsettled() if s.split(" ")[1] in ours])
w = why_not(finance.Offer.hom1.cheapest)
print(w)
try:
t = finance.total("hom1")
print(t)
except refuse as r:
print(r)
totals = list_values(finance.Offer.total)
for v in totals:
if v.path.split(".")[2] in ours:
print(v.path, v.value)['hom1'] []
[]
[] ['hom1']
['offer hom1 payment']
rule `finance.cheapest_offer` needs finance.Offer[hom1].cheapest_offer__none1 to be absent, and it is present
refuse: cell holds 2 competing values — the single-claim read refuses; use `get` to read the full claim set
finance.Offer.hom1.total 133143.82660966867
finance.Offer.hom1.total 162000.0
finance.Offer.hom2.total 142342.85281347804With one source, the 4% loan is the cheapest and is under the limit of 800. With the second, hom1 holds two payments, 739.69 and 900, and so two totals, 133,143.83 and 162,000. The list of unaffordable offers names it, because the second payment is over 800. The list of the cheapest is empty, and not because the library lost the thing: each offer of the group now has a total that another total in force beats, the dear total of hom1 being beaten by hom2's 142,342.85 and hom2's by the cheap total of hom1. why_not says of hom1 that the absence the rule needs does not hold, a cheaper offer being present. unsettled() names the fact, offer hom1 payment, and that is the fact to settle: until a reader decides which payment the lender stated, the comparison has no answer, and the library leaves it so. total refuses, and says that the cell holds two competing values.
5. What a rate says it is
The arithmetic of a rate is the arithmetic of compounding. A sum P at a nominal annual rate r compounded n times a year grows in t years to A = P(1 + r/n)^(nt). The annual equivalent has several names: the effective annual rate, the annual equivalent rate, AER, and the annual percentage yield. As the periods grow without bound, the effective annual rate approaches an upper limit of e^r - 1. The constant e itself came out of a question about compound interest: Jacob Bernoulli discovered it in 1683 by studying one. The library has all three, with the rate a fraction:
return math.expm1(periods_per_year * math.log1p(q))return math.expm1(annual_rate)The law and the regulation give the effective rate a place. In the United States the Truth in Lending Act governs the calculation and disclosure of the annual percentage rate and it is implemented by the Consumer Financial Protection Bureau through Regulation Z; in the European Union a single method of calculating the rate was introduced in 1998, in directive 98/7/EC, and it makes the present value of the lender's drawdowns equal to the present value of the borrower's repayments. A card quoted at 12.99% compounded monthly costs more than 12.99% over a year, and the page on the rate works it with a rate of 0.12994912, which it does not explain, and gets 13.7975 percent. A card at 29.99% compounded monthly carries an effective rate of 34.48 percent:
import finance
print(finance.effective_annual_rate(0.1299, 12.0), finance.effective_annual_rate(0.2999, 12.0))
print(finance.effective_annual_rate(0.12, 1.0), finance.effective_annual_rate(0.12, 12.0), finance.effective_annual_rate(0.12, 365.0), finance.continuous_annual_rate(0.12))
print(finance.compound_amount(1000.0, 0.05, 1.0, 2.0), finance.compound_amount(10000.0, 0.15, 1.0, 40.0))0.1379199031259423 0.34475762144741345
0.12 0.12682503013196972 0.12747461563840262 0.12749685157937565
1102.5 2678635.4623470255At exactly 12.99 percent the formula gives 13.792 percent, not the page's 13.7975, whose rate is a little higher than the quoted one; at 29.99 it gives 34.4758, and the page's 34.48. A rate of 12% compounded once a year should be 12%, and the library gives 0.12, because it never forms the number 1.12 and subtracts 1 from it, which would leave the double's error in the last digit; q is the rate over the periods. Compounded monthly it is 12.68%, daily 12.75%, and continuously 12.7497%, which is as far as more frequent compounding goes. The article on compound interest says that $1,000 at 5% a year is worth $1,050 after one year and $1,102.50 after two, because the second year's interest is calculated on the new value, and the library's 1102.5 is that. The article's figure of a 15% compounding on an initial $10,000 over 40 years, inflation ignored, is 2,678,635 here.
6. The doubling time
How long it takes a sum to double is the oldest question of the subject that a person can do in their head. The exact time is ln(2)/ln(1 + r/100) periods for r percent, and the rule of 72 says 72/r. The rule is in Luca Pacioli's Summa de arithmetica of 1494, which presents it in a discussion of estimating the doubling time of an investment. It suits rates from 6 to 10 percent and is less accurate at higher ones, and for continuous compounding 69 gives accurate results for any rate. The two scripts take percent, as the rule is written:
return math.log(2.0) / math.log(1.0 + percent / 100.0)return 72.0 / percentimport finance
for percent in [6.0, 8.0, 10.0, 12.0, 20.0, 30.0]:
print(percent, round(finance.doubling_exact(percent), 3), finance.doubling_rule_of_72(percent))6.0 11.896 12.0
8.0 9.006 9.0
10.0 7.273 7.2
12.0 6.116 6.0
20.0 3.802 3.6
30.0 2.642 2.4At 8 percent the exact time is 9.006 years and the rule gives 9.0, which is the figure the page gives for it. The rows for 6, 10, 12, 20 and 30 percent are those of the page's table: 11.896 against 12.0, 7.273 against 7.2, 6.116 against 6.0, 3.802 against 3.6 and 2.642 against 2.4. The rule is closest near 8 percent, and its error grows on either side. The logarithm here is correctly rounded, and CPython's can differ from it by one unit in the last place at some arguments, so the exact time agrees with CPython's to that unit and not always to the last bit. A rate of zero never doubles and refuses with a division by zero; a rate of -100 percent refuses with a math domain error.
7. Value over time
A sum in the future is worth less now. The article on present value states it as a sum discounted at the periodic rate, and says "A dollar today is worth more than a dollar tomorrow". It gives the example that $1,000 in five years at an effective rate of 10 percent is worth $620.92 now, so that a person would be indifferent between receiving $1,000 in five years or $620.92 today. The practice is old: the page on present value notes that the English crown used a method of years' purchase for manors seized at the Dissolution of the Monasteries in the early 16th century, the standard usage being 20 years' purchase. The same discounting with the rate of inflation gives a nominal sum in today's money, and the Fisher equation relates a nominal rate to a real one. It is (1 + i) = (1 + r)(1 + pi) for the nominal rate i, the real rate r and the rate of inflation pi, with the approximation r = i - pi often used instead, because the rates are usually close to zero. Irving Fisher's works The Rate of Interest of 1907 and The Theory of Interest of 1930 are cited on the page. The library has the exact form:
v = future / (1.0 + annual_rate) ** yearsreturn (1.0 + nominal_rate) / (1.0 + inflation) - 1.0import finance
print(finance.present_value(1000.0, 0.10, 5.0), finance.todays_value(1000.0, 0.03, 20.0))
print(finance.real_rate(0.05, 0.03), 0.05 - 0.03)620.9213230591549 553.6757541863345
0.01941747572815533 0.020000000000000004The 620.9213 is the page's $620.92. A thousand units in twenty years, at a steady inflation of 3% a year, is 553.68 in today's money. A nominal rate of 5% with inflation of 3% is a real rate of 1.94%, where the approximation says 2%; the exact form is lower, and the gap grows with the rates.
8. What the library leaves out
The library leaves out the part of a loan that is not arithmetic. It has no fees folded into an annual percentage rate, no taxes, no schedule row by row, no variable rates, and no statute beyond the words of the sources named here. The module rounds nothing to the cent, so it cannot reproduce a lender's statement. It compares offers by total and by a limit that you give it; it does not know whether the limit is the right one, whether a household can bear a lower payment, or what else the money is for. A rate is a fraction everywhere but in the two doubling scripts, which take percent as the rule is written.
Closing
The history of lending is a history of arguing about what a promise to pay later should cost. The Sumerians recorded the debt, the council in 325 set a ceiling for its clergy, Pacioli wrote down a rule for doubling, and Bernoulli found a constant in a question about interest. The library keeps the arithmetic where a reader can run it and ask about it. It marks the cheapest offer and the offer over a limit as separate facts and lets one offer be both, calls offers of equal total each the cheapest and ranks nothing, holds two quotations of a payment side by side and names the fact they disagree about, and refuses where no value exists. And because each conclusion names the facts it rests on, a payment corrected in one place is corrected in every verdict that rested on it.