- class
- __init__
- method
- inheritance
- OOP
- Bond pricing
Object-Oriented Programming — Modelling Financial Instruments
Scene: your first week on a fixed-income desk. The portfolio manager leans over: "The Fed just moved — 10-year yields went from 4% to 5%. Reprice the book." The book holds thousands of bonds. Nobody reprices them one spreadsheet cell at a time: every real trading system represents each instrument as an object — a bundle of data (face value, coupon, maturity) plus behaviour (price(ytm)). Change the yield, ask every object to reprice itself, done before the coffee cools.
Python's class system maps perfectly onto financial hierarchies — and today you build one.
📜 The 800-Year History of Bond Pricing
The present value formula you are about to code is one of the oldest mathematical ideas in finance — and one of the most consequential.
1202 — Leonardo Fibonacci (Liber Abaci): Introduced present-value reasoning to Western merchants in the context of comparing loans with different repayment schedules. He described how to find the "fair" present equivalent of future money — the core of bond pricing.
1694 — Bank of England founded: The British government issued the first modern government bonds (gilts) to fund wars. Pricing these bonds consistently required formalising the relationship between coupon, maturity, and yield.
1907 — Irving Fisher (The Rate of Interest): Fully formalised the theory of present value and why rational investors discount the future. His equation PV = Σ Cₜ / (1+r)ᵗ is exactly what you will implement. Fisher also invented the concept of "real vs nominal" interest rates (still taught as the Fisher equation today).
1938 — Frederick Macaulay introduced bond duration — a measure of a bond's price sensitivity to yield changes. He computed it as the weighted average time to receive cash flows. This single number allows traders to hedge interest rate risk, and it is still used in exactly this form in every fixed income desk today.
1938 — John Burr Williams (The Theory of Investment Value): Applied the same PV formula to stocks — a stock is worth the discounted value of all future dividends. This became the foundation of Discounted Cash Flow (DCF) analysis.
1962 — Burton Malkiel formalised the 5 Theorems of Bond Pricing — still reproduced verbatim in CFA textbooks:
- ›Bond prices move inversely to yields
- ›Longer maturity → greater price sensitivity
- ›Price sensitivity increases at a decreasing rate as maturity increases
- ›Price rises more than it falls for equal yield changes (convexity) — a yield drop lifts the price more than an equal yield rise cuts it
- ›Lower-coupon bonds are more sensitive to yield changes
When you write pv_coupons + pv_face, you are implementing 800 years of financial mathematics.
Class Hierarchy — UML Diagram
Here is the inheritance structure you are about to build:
🧠 The mental model: term sheet vs signed deal
- ›A class is the blank term sheet.
class Bond:defines what every bond must specify (face value, coupon rate, maturity) and what every bond can do (price(),describe()). No money moves yet. - ›An object is one signed deal.
Bond("US10Y", 1000, 0.045, 10)fills the template with real numbers. Fill it in twice with different numbers and you hold two independent deals. - ›
selfmeans "this particular deal". Insideprice(),self.coupon_ratereads "the coupon rate of the bond this method was called on" — one method reprices US Treasuries and German Bunds alike. - ›Inheritance is the org chart. A Stock is a FinancialInstrument; so is a Bond. Both automatically carry the parent's
ticker,currencyanddescribe()— like subsidiaries inheriting the group's branding.
Class Basics
Pythonclass FinancialInstrument: def __init__(self, ticker, currency="USD"): self.ticker = ticker self.currency = currency def describe(self): return f"{self.ticker} ({self.currency})"
Inheritance — super().__init__()
Pythonclass Stock(FinancialInstrument): def __init__(self, ticker, shares_outstanding, currency="USD"): super().__init__(ticker, currency) # call parent first self.shares_outstanding = shares_outstanding def market_cap(self, price): return price * self.shares_outstanding
Why
super()? It delegates initialisation to the parent class, so you don't repeat code. This is the foundation of DRY (Don't Repeat Yourself) in OOP.
Bond Pricing — Present Value of Cash Flows
A bond pays a coupon each year and returns the face value at maturity. Its fair price is exactly Irving Fisher's 1907 formula — every future cash flow, discounted back to today:
P = C/(1+y)¹ + C/(1+y)² + ... + C/(1+y)ᵀ + F/(1+y)ᵀ
└──────── PV of the coupon stream ────────┘ └ PV of face ┘
where C = annual coupon payment (face value × coupon rate), y = yield to maturity, T = years to maturity, F = face value.
The formula carries a built-in sanity check — Malkiel's theorem #1 in miniature:
| Situation | Price vs face value | Name |
|---|---|---|
| coupon rate < market yield | below 1000 | discount bond |
| coupon rate = market yield | exactly 1000 | par bond |
| coupon rate > market yield | above 1000 | premium bond |
Your Task
The starter code builds the entire class hierarchy — but Bond.price() currently returns the face value no matter what the yield is, as if discounting had never been invented. Run it as-is: every bond reports 1000.00 USD, "at par", at every yield. Pre-Fibonacci finance.
Your job is the method body at the 👉 YOUR TURN marker: implement Fisher's formula — discount the coupon stream, discount the face value, return pv_coupons + pv_face.
Predict before you run: the bond pays a fixed 4.5% coupon. Priced at a 5.0% market yield, will it be worth more or less than 1000? And at 4.0%? Use the table above, write both answers down, then run — the output tags each price as discount/premium/par so you can grade your own prediction like a desk quant.