Building Financial Statements

BusinessMath Quarterly Series

14 min read

Part 14 of 12-Week BusinessMath Series


What You’ll Learn


The Problem

Financial statements are the foundation of business analysis. Every valuation, credit decision, and strategic plan starts with: Building these statements manually is tedious and error-prone. You need to: You need a structured, type-safe way to model financial statements programmatically.

The Solution

BusinessMath provides IncomeStatement, 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())")
→ Full API Reference: BusinessMath Docs – 3.5 Financial Statements

Modifications to try:

  1. Add a Cash Flow Statement with operating, investing, and financing activities
  2. Model multiple years of annual statements
  3. Create pro forma statements for forecasting

Real-World Application

Every three-statement model starts here: BusinessMath makes statement modeling type-safe, validated, and composable.
★ Insight ─────────────────────────────────────

Why Use Role Enums Instead of Generic Types?

You could use generic type: .expense for all expenses.

But role-specific enums provide:

This prevents errors like classifying interest as operating expense or mixing incompatible accounts.

─────────────────────────────────────────────────


📝 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:

We chose separate statement types that compose when needed. More flexible, slightly more verbose.

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:


Tagged with: financial-analysis