Building Financial Statements
BusinessMath Quarterly Series
14 min read
Part 14 of 12-Week BusinessMath Series
What You’ll Learn
- Creating Income Statements with revenue and expense accounts
- Building Balance Sheets with assets, liabilities, and equity
- Modeling Cash Flow Statements with operating, investing, and financing activities
- Verifying the accounting equation (Assets = Liabilities + Equity)
- Computing key metrics automatically from statements
The Problem
Financial statements are the foundation of business analysis. Every valuation, credit decision, and strategic plan starts with:- Income Statement: Is the company profitable?
- Balance Sheet: What does the company own and owe?
- Cash Flow Statement: Is the company generating cash?
- Track accounts across multiple periods
- Ensure accounts are properly classified
- Calculate subtotals (gross profit, operating income, EBITDA)
- Verify accounting equations balance
- Compute ratios from the statements
The Solution
BusinessMath providesIncomeStatement,
BalanceSheet, and
CashFlowStatement types that handle classification, computation, and validation automatically.
Creating an Entity
Every financial model starts with an entity:import BusinessMath
let acme = Entity(
id: “ACME001”,
primaryType: .ticker,
name: “Acme Corporation”,
identifiers: [.ticker: “ACME”],
currency: “USD”
)
Building an Income Statement
The Income Statement shows profitability over time:// Define periods
let q1 = Period.quarter(year: 2025, quarter: 1)
let q2 = Period.quarter(year: 2025, quarter: 2)
let q3 = Period.quarter(year: 2025, quarter: 3)
let q4 = Period.quarter(year: 2025, quarter: 4)
let periods = [q1, q2, q3, q4]
// Revenue account
let revenue = try Account(
entity: acme,
name: “Product Revenue”,
incomeStatementRole: .productRevenue,
timeSeries: TimeSeries(
periods: periods,
values: [1_000_000, 1_100_000, 1_200_000, 1_300_000]
)
)
// Cost of Goods Sold
let cogs = try Account(
entity: acme,
name: “Cost of Goods Sold”,
incomeStatementRole: .costOfGoodsSold,
timeSeries: TimeSeries(
periods: periods,
values: [400_000, 440_000, 480_000, 520_000]
)
)
// Operating Expenses
let salaries = try Account(
entity: acme,
name: “Salaries”,
incomeStatementRole: .generalAndAdministrative,
timeSeries: TimeSeries(
periods: periods,
values: [200_000, 200_000, 200_000, 200_000]
)
)
let marketing = try Account(
entity: acme,
name: “Marketing”,
incomeStatementRole: .salesAndMarketing,
timeSeries: TimeSeries(
periods: periods,
values: [50_000, 60_000, 70_000, 80_000]
)
)
let depreciation = try Account(
entity: acme,
name: “Depreciation”,
incomeStatementRole: .depreciationAmortization,
timeSeries: TimeSeries(
periods: periods,
values: [50_000, 50_000, 50_000, 50_000]
)
)
// Interest and Taxes
let interestExpense = try Account(
entity: acme,
name: “Interest Expense”,
incomeStatementRole: .interestExpense,
timeSeries: TimeSeries(
periods: periods,
values: [10_000, 10_000, 10_000, 10_000]
)
)
let incomeTax = try Account(
entity: acme,
name: “Income Tax”,
incomeStatementRole: .incomeTaxExpense,
timeSeries: TimeSeries(
periods: periods,
values: [60_000, 69_000, 78_000, 87_000]
)
)
// Create Income Statement
let incomeStatement = try IncomeStatement(
entity: acme,
periods: periods,
accounts: [revenue, cogs, salaries, marketing, depreciation, interestExpense, incomeTax]
)
// Access computed values
print(”=== Q1 2025 Income Statement ===\n”)
print(“Revenue:\t\t(incomeStatement.totalRevenue[q1]!.currency())”)
print(“COGS:\t\t\t((cogs.timeSeries[q1]!.currency()))”)
print(“Gross Profit:\t\t(incomeStatement.grossProfit[q1]!.currency())”)
print(” Gross Margin:\t\t(incomeStatement.grossMargin[q1]!.percent(1))”)
print()
print(“Operating Expenses:\t(((salaries.timeSeries[q1]! + marketing.timeSeries[q1]! + depreciation.timeSeries[q1]!).currency()))”)
print(“Operating Income:\t(incomeStatement.operatingIncome[q1]!.currency())”)
print(” Operating Margin:\t(incomeStatement.operatingMargin[q1]!.percent(1))”)
print()
print(“EBITDA:\t\t\t(incomeStatement.ebitda[q1]!.currency())”)
print(” EBITDA Margin:\t\t(incomeStatement.ebitdaMargin[q1]!.percent(1))”)
print()
print(“Interest Expense:\t((interestExpense.timeSeries[q1]!.currency()))”)
print(“Income Tax:\t\t((incomeTax.timeSeries[q1]!.currency()))”)
print(“Net Income:\t\t(incomeStatement.netIncome[q1]!.currency())”)
print(” Net Margin:\t\t(incomeStatement.netMargin[q1]!.percent(1))”)
Output:
=== Q1 2025 Income Statement ===
Revenue: $1,000,000
COGS: ($400,000)
Gross Profit: $600,000
Gross Margin: 60.0%
Operating Expenses: ($300,000)
Operating Income: $300,000
Operating Margin: 30.0%
EBITDA: $350,000
EBITDA Margin: 35.0%
Interest Expense: ($10,000)
Income Tax: ($60,000)
Net Income: $230,000
Net Margin: 23.0%
The power: Income Statement automatically computes gross profit, operating income, EBITDA, and all margins. No manual calculations.
Building a Balance Sheet
The Balance Sheet shows financial position:// Assets
let cash = try Account(
entity: acme,
name: “Cash and Equivalents”,
balanceSheetRole: .cashAndEquivalents,
timeSeries: TimeSeries(
periods: periods,
values: [500_000, 600_000, 750_000, 900_000]
)
)
let receivables = try Account(
entity: acme,
name: “Accounts Receivable”,
balanceSheetRole: .accountsReceivable,
timeSeries: TimeSeries(
periods: periods,
values: [300_000, 330_000, 360_000, 390_000]
)
)
let inventory = try Account(
entity: acme,
name: “Inventory”,
balanceSheetRole: .inventory,
timeSeries: TimeSeries(
periods: periods,
values: [200_000, 220_000, 240_000, 260_000]
)
)
let ppe = try Account(
entity: acme,
name: “Property, Plant & Equipment”,
balanceSheetRole: .propertyPlantEquipment,
timeSeries: TimeSeries(
periods: periods,
values: [1_000_000, 980_000, 960_000, 940_000]
)
)
// Liabilities
let payables = try Account(
entity: acme,
name: “Accounts Payable”,
balanceSheetRole: .accountsPayable,
timeSeries: TimeSeries(
periods: periods,
values: [150_000, 165_000, 180_000, 195_000]
)
)
let longTermDebt = try Account(
entity: acme,
name: “Long-term Debt”,
balanceSheetRole: .longTermDebt,
timeSeries: TimeSeries(
periods: periods,
values: [500_000, 500_000, 500_000, 500_000]
)
)
// Equity
let commonStock = try Account(
entity: acme,
name: “Common Stock”,
balanceSheetRole: .commonStock,
timeSeries: TimeSeries(
periods: periods,
values: [1_000_000, 1_000_000, 1_000_000, 1_000_000]
)
)
let retainedEarnings = try Account(
entity: acme,
name: “Retained Earnings”,
balanceSheetRole: .retainedEarnings,
timeSeries: TimeSeries(
periods: periods,
values: [350_000, 465_000, 630_000, 805_000]
)
)
// Create Balance Sheet
let balanceSheet = try BalanceSheet(
entity: acme,
periods: periods,
accounts: [cash, receivables, inventory, ppe, payables, longTermDebt, commonStock, retainedEarnings]
)
// Print Balance Sheet
print(”\n=== Q1 2025 Balance Sheet ===\n”)
print(“ASSETS”)
print(“Current Assets:”)
print(” Cash:\t\t\t(cash.timeSeries[q1]!.currency())”)
print(” Receivables:\t\t(receivables.timeSeries[q1]!.currency())”)
print(” Inventory:\t\t(inventory.timeSeries[q1]!.currency())”)
print(” Total Current:\t(balanceSheet.currentAssets[q1]!.currency())”)
print()
print(“Fixed Assets:”)
print(” PP&E:\t\t\t(ppe.timeSeries[q1]!.currency())”)
print()
print(“Total Assets:\t\t(balanceSheet.totalAssets[q1]!.currency())”)
print()
print(“LIABILITIES”)
print(“Current Liabilities:”)
print(” Payables:\t\t(payables.timeSeries[q1]!.currency())”)
print()
print(“Long-term Liabilities:”)
print(” Debt:\t\t\t(longTermDebt.timeSeries[q1]!.currency())”)
print()
print(“Total Liabilities:\t(balanceSheet.totalLiabilities[q1]!.currency())”)
print()
print(“EQUITY”)
print(” Common Stock:\t\t(commonStock.timeSeries[q1]!.currency())”)
print(” Retained Earnings:\t(retainedEarnings.timeSeries[q1]!.currency())”)
print(“Total Equity:\t\t(balanceSheet.totalEquity[q1]!.currency())”)
print()
print(“Total Liab + Equity:\t((balanceSheet.totalLiabilities[q1]! + balanceSheet.totalEquity[q1]!).currency()))”)
// Verify accounting equation
let assets = balanceSheet.totalAssets[q1]!
let liabilities = balanceSheet.totalLiabilities[q1]!
let equity = balanceSheet.totalEquity[q1]!
print(”\n✓ Balance Check: Assets ((assets.currency())) = Liabilities + Equity (((liabilities + equity).currency()))”)
print(” Balanced: (assets == liabilities + equity)”)
// Calculate ratios
print(”\nKey Ratios:”)
print(” Current Ratio:\t\t(balanceSheet.currentRatio[q1]!.number(2))x”)
print(” Debt-to-Equity:\t\t(balanceSheet.debtToEquity[q1]!.number(2))x”)
print(” Equity Ratio:\t\t(balanceSheet.equityRatio[q1]!.percent(1))”)
Output:
=== Q1 2025 Balance Sheet ===
ASSETS
Current Assets:
Cash: $500,000
Receivables: $300,000
Inventory: $200,000
Total Current: $1,000,000
Fixed Assets:
PP&E: $1,000,000
Total Assets: $2,000,000
LIABILITIES
Current Liabilities:
Payables: $150,000
Long-term Liabilities:
Debt: $500,000
Total Liabilities: $650,000
EQUITY
Common Stock: $1,000,000
Retained Earnings: $350,000
Total Equity: $1,350,000
Total Liab + Equity: $2,000,000
✓ Balance Check: Assets ($2,000,000) = Liabilities + Equity ($2,000,000)
Balanced: true
Key Ratios:
Current Ratio: 6.67x
Debt-to-Equity: 0.37x
Equity Ratio: 67.5%
The insight: Balance Sheet automatically validates Assets = Liabilities + Equity and computes liquidity/leverage ratios.
Linking Statements Together
Retained Earnings bridges Income Statement and Balance Sheet:// Verify retained earnings flow
let beginningRE = retainedEarnings.timeSeries[q1]! // $350,000
let netIncome = incomeStatement.netIncome[q1]! // $230,000 (calculated earlier)
let dividends = 0.0 // No dividends paid in Q1
let endingRE = retainedEarnings.timeSeries[q2]! // $465,000
let calculatedEndingRE = beginningRE + netIncome - dividends
print(”\n=== Retained Earnings Reconciliation ===”)
print(“Beginning (Q1): (beginningRE.currency())”)
print(”+ Net Income: (netIncome.currency())”)
print(”- Dividends: (dividends.currency())”)
print(”= Ending (Q2): (calculatedEndingRE.currency())”)
print(”\nActual Q2 RE: (endingRE.currency())”)
print(“Difference: ((endingRE - calculatedEndingRE).currency())”)
This links the statements: Net income flows from Income Statement → Retained Earnings on Balance Sheet.
Try It Yourself
Click to expand full playground code
import BusinessMath
let acme = Entity(
id: "ACME001",
primaryType: .ticker,
name: "Acme Corporation",
identifiers: [.ticker: "ACME"],
currency: "USD"
)
// Define periods
let q1 = Period.quarter(year: 2025, quarter: 1)
let q2 = Period.quarter(year: 2025, quarter: 2)
let q3 = Period.quarter(year: 2025, quarter: 3)
let q4 = Period.quarter(year: 2025, quarter: 4)
let periods = [q1, q2, q3, q4]
// Revenue account
let revenue = try Account(
entity: acme,
name: "Product Revenue",
incomeStatementRole: .productRevenue,
timeSeries: TimeSeries(
periods: periods,
values: [1_000_000, 1_100_000, 1_200_000, 1_300_000]
)
)
// Cost of Goods Sold
let cogs = try Account(
entity: acme,
name: "Cost of Goods Sold",
incomeStatementRole: .costOfGoodsSold,
timeSeries: TimeSeries(
periods: periods,
values: [400_000, 440_000, 480_000, 520_000]
)
)
// Operating Expenses
let salaries = try Account(
entity: acme,
name: "Salaries",
incomeStatementRole: .generalAndAdministrative,
timeSeries: TimeSeries(
periods: periods,
values: [200_000, 200_000, 200_000, 200_000]
)
)
let marketing = try Account(
entity: acme,
name: "Marketing",
incomeStatementRole: .salesAndMarketing,
timeSeries: TimeSeries(
periods: periods,
values: [50_000, 60_000, 70_000, 80_000]
)
)
let depreciation = try Account(
entity: acme,
name: "Depreciation",
incomeStatementRole: .depreciationAmortization,
timeSeries: TimeSeries(
periods: periods,
values: [50_000, 50_000, 50_000, 50_000]
)
)
// Interest and Taxes
let interestExpense = try Account(
entity: acme,
name: "Interest Expense",
incomeStatementRole: .interestExpense,
timeSeries: TimeSeries(
periods: periods,
values: [10_000, 10_000, 10_000, 10_000]
)
)
let incomeTax = try Account(
entity: acme,
name: "Income Tax",
incomeStatementRole: .incomeTaxExpense,
timeSeries: TimeSeries(
periods: periods,
values: [60_000, 69_000, 78_000, 87_000]
)
)
// Create Income Statement
let incomeStatement = try IncomeStatement(
entity: acme,
periods: periods,
accounts: [revenue, cogs, salaries, marketing, depreciation, interestExpense, incomeTax]
)
// Access computed values
print("=== Q1 2025 Income Statement ===\n")
print("Revenue:\t\t\(incomeStatement.totalRevenue[q1]!.currency())")
print("COGS:\t\t\t(\(cogs.timeSeries[q1]!.currency()))")
print("Gross Profit:\t\t\(incomeStatement.grossProfit[q1]!.currency())")
print(" Gross Margin:\t\t\(incomeStatement.grossMargin[q1]!.percent(1))")
print()
print("Operating Expenses:\t(\((salaries.timeSeries[q1]! + marketing.timeSeries[q1]! + depreciation.timeSeries[q1]!).currency()))")
print("Operating Income:\t\(incomeStatement.operatingIncome[q1]!.currency())")
print(" Operating Margin:\t\(incomeStatement.operatingMargin[q1]!.percent(1))")
print()
print("EBITDA:\t\t\t\(incomeStatement.ebitda[q1]!.currency())")
print(" EBITDA Margin:\t\t\(incomeStatement.ebitdaMargin[q1]!.percent(1))")
print()
print("Interest Expense:\t(\(interestExpense.timeSeries[q1]!.currency()))")
print("Income Tax:\t\t(\(incomeTax.timeSeries[q1]!.currency()))")
print("Net Income:\t\t\(incomeStatement.netIncome[q1]!.currency())")
print(" Net Margin:\t\t\(incomeStatement.netMargin[q1]!.percent(1))")
// Assets
let cash = try Account(
entity: acme,
name: "Cash and Equivalents",
balanceSheetRole: .cashAndEquivalents,
timeSeries: TimeSeries(
periods: periods,
values: [500_000, 600_000, 750_000, 900_000]
)
)
let receivables = try Account(
entity: acme,
name: "Accounts Receivable",
balanceSheetRole: .accountsReceivable,
timeSeries: TimeSeries(
periods: periods,
values: [300_000, 330_000, 360_000, 390_000]
)
)
let inventory = try Account(
entity: acme,
name: "Inventory",
balanceSheetRole: .inventory,
timeSeries: TimeSeries(
periods: periods,
values: [200_000, 220_000, 240_000, 260_000]
)
)
let ppe = try Account(
entity: acme,
name: "Property, Plant & Equipment",
balanceSheetRole: .propertyPlantEquipment,
timeSeries: TimeSeries(
periods: periods,
values: [1_000_000, 980_000, 960_000, 940_000]
)
)
// Liabilities
let payables = try Account(
entity: acme,
name: "Accounts Payable",
balanceSheetRole: .accountsPayable,
timeSeries: TimeSeries(
periods: periods,
values: [150_000, 165_000, 180_000, 195_000]
)
)
let longTermDebt = try Account(
entity: acme,
name: "Long-term Debt",
balanceSheetRole: .longTermDebt,
timeSeries: TimeSeries(
periods: periods,
values: [500_000, 500_000, 500_000, 500_000]
)
)
// Equity
let commonStock = try Account(
entity: acme,
name: "Common Stock",
balanceSheetRole: .commonStock,
timeSeries: TimeSeries(
periods: periods,
values: [1_000_000, 1_000_000, 1_000_000, 1_000_000]
)
)
let retainedEarnings = try Account(
entity: acme,
name: "Retained Earnings",
balanceSheetRole: .retainedEarnings,
timeSeries: TimeSeries(
periods: periods,
values: [350_000, 465_000, 630_000, 805_000]
)
)
// Create Balance Sheet
let balanceSheet = try BalanceSheet(
entity: acme,
periods: periods,
accounts: [cash, receivables, inventory, ppe, payables, longTermDebt, commonStock, retainedEarnings]
)
// Print Balance Sheet
print("\n=== Q1 2025 Balance Sheet ===\n")
print("ASSETS")
print("Current Assets:")
print(" Cash:\t\t\t\(cash.timeSeries[q1]!.currency())")
print(" Receivables:\t\t\(receivables.timeSeries[q1]!.currency())")
print(" Inventory:\t\t\(inventory.timeSeries[q1]!.currency())")
print(" Total Current:\t\(balanceSheet.currentAssets[q1]!.currency())")
print()
print("Fixed Assets:")
print(" PP&E:\t\t\t\(ppe.timeSeries[q1]!.currency())")
print()
print("Total Assets:\t\t\(balanceSheet.totalAssets[q1]!.currency())")
print()
print("LIABILITIES")
print("Current Liabilities:")
print(" Payables:\t\t\(payables.timeSeries[q1]!.currency())")
print()
print("Long-term Liabilities:")
print(" Debt:\t\t\t\(longTermDebt.timeSeries[q1]!.currency())")
print()
print("Total Liabilities:\t\(balanceSheet.totalLiabilities[q1]!.currency())")
print()
print("EQUITY")
print(" Common Stock:\t\t\(commonStock.timeSeries[q1]!.currency())")
print(" Retained Earnings:\t\(retainedEarnings.timeSeries[q1]!.currency())")
print("Total Equity:\t\t\(balanceSheet.totalEquity[q1]!.currency())")
print()
print("Total Liab + Equity:\t\((balanceSheet.totalLiabilities[q1]! + balanceSheet.totalEquity[q1]!).currency()))")
// Verify accounting equation
let assets = balanceSheet.totalAssets[q1]!
let liabilities = balanceSheet.totalLiabilities[q1]!
let equity = balanceSheet.totalEquity[q1]!
print("\n✓ Balance Check: Assets (\(assets.currency())) = Liabilities + Equity (\((liabilities + equity).currency()))")
print(" Balanced: \(assets == liabilities + equity)")
// Calculate ratios
print("\nKey Ratios:")
print(" Current Ratio:\t\t\(balanceSheet.currentRatio[q1]!.number(2))x")
print(" Debt-to-Equity:\t\t\(balanceSheet.debtToEquity[q1]!.number(2))x")
print(" Equity Ratio:\t\t\(balanceSheet.equityRatio[q1]!.percent(1))")
// Verify retained earnings flow
let beginningRE = retainedEarnings.timeSeries[q1]! // $350,000
let netIncome = incomeStatement.netIncome[q1]! // $230,000 (calculated earlier)
let dividends = 0.0 // No dividends paid in Q1
let endingRE = retainedEarnings.timeSeries[q2]! // $465,000
let calculatedEndingRE = beginningRE + netIncome - dividends
print("\n=== Retained Earnings Reconciliation ===")
print("Beginning (Q1): \(beginningRE.currency())")
print("+ Net Income: \(netIncome.currency())")
print("- Dividends: \(dividends.currency())")
print("= Ending (Q2): \(calculatedEndingRE.currency())")
print("\nActual Q2 RE: \(endingRE.currency())")
print("Difference: \((endingRE - calculatedEndingRE).currency())")
Modifications to try:
- Add a Cash Flow Statement with operating, investing, and financing activities
- Model multiple years of annual statements
- Create pro forma statements for forecasting
Real-World Application
Every three-statement model starts here:- Investment banking: Modeling LBO returns
- Corporate finance: Budgeting and planning
- Equity research: Forecasting earnings
- Credit analysis: Assessing solvency
★ Insight ─────────────────────────────────────
Why Use Role Enums Instead of Generic Types?
You could use generic type: .expense for all expenses.
But role-specific enums provide:
- Explicit classification:
incomeStatementRole: .costOfGoodsSoldmakes intent clear - Type safety: Can’t accidentally treat COGS as operating expense
- Automatic aggregation: Multiple accounts with same role aggregate automatically
- Multi-role capability: Same account (e.g., D&A) can have both Income Statement and Cash Flow roles
- Statement validation: Ensures only valid roles are used per statement type
─────────────────────────────────────────────────
📝 Development Note
The hardest part of financial statement modeling was deciding: How much abstraction?We could have made a single FinancialStatements class with all three statements bundled. But different analyses need different statements:
- Valuation: Needs Income Statement and Cash Flow Statement
- Credit analysis: Needs Balance Sheet and Cash Flow Statement
- Profitability: Needs only Income Statement
Related Methodology: The Master Plan (Week 3) - Managing API surface area
Next Steps
Coming up next: Lease Accounting (Friday) - IFRS 16 / ASC 842 lease modeling with right-of-use assets and lease liabilities.Next week: Week 5 explores loans, investments, and valuations.
Series Progress:
- Week: 4/12
- Posts Published: 14/~48
- Topics Covered: Foundation + Analysis + Operational + Financial Statements (in progress)
- Playgrounds: 13 available
Tagged with: financial-analysis