BusinessMath Quarterly Series
11 min read
Part 13 of 12-Week BusinessMath Series
Financial analysis isn’t just about individual statements—it’s about trends, comparisons, and integrated metrics. Analysts need to see:
Building comprehensive multi-period reports manually can be tedious. You need financial statements, operational metrics, computed ratios, and trend calculations—all integrated into a cohesive view.
BusinessMath provides a system that combines statements, metrics, and analytics automatically.
BusinessMath provides FinancialPeriodSummary and MultiPeriodReport for analyst-quality financial reporting.
Start with Income Statement and Balance Sheet for multiple periods:
import BusinessMath
let entity = Entity(
id: "ACME",
primaryType: .ticker,
name: "Acme Corporation"
)
let periods = (1...4).map { Period.quarter(year: 2025, quarter: $0) }
// Revenue account
let revenue = try Account(
entity: entity,
name: "Product Revenue",
incomeStatementRole: .revenue,
timeSeries: TimeSeries(periods: periods, values: [1_000_000, 1_100_000, 1_200_000, 1_300_000])
)
// Expense accounts
let cogs = try Account(
entity: entity,
name: "Cost of Goods Sold",
incomeStatementRole: .costOfGoodsSold,
timeSeries: TimeSeries(periods: periods, values: [400_000, 450_000, 480_000, 520_000])
)
let opex = try Account(
entity: entity,
name: "Operating Expenses",
incomeStatementRole: .operatingExpenseOther,
timeSeries: TimeSeries(periods: periods, values: [300_000, 325_000, 350_000, 375_000])
)
let depreciation = try Account(
entity: entity,
name: "Depreciation",
incomeStatementRole: .depreciationAmortization,
timeSeries: TimeSeries(periods: periods, values: [50_000, 50_000, 50_000, 50_000])
)
let interest = try Account(
entity: entity,
name: "Interest Expense",
incomeStatementRole: .interestExpense,
timeSeries: TimeSeries(periods: periods, values: [25_000, 25_000, 25_000, 25_000])
)
let tax = try Account(
entity: entity,
name: "Income Tax",
incomeStatementRole: .incomeTaxExpense,
timeSeries: TimeSeries(periods: periods, values: [47_000, 49_000, 61_000, 68_000])
)
// Create Income Statement
let incomeStatement = try IncomeStatement(
entity: entity,
periods: periods,
accounts: [revenue, cogs, opex, depreciation, interest, tax]
)
// Create Balance Sheet (assets, liabilities, equity)
let cash = try Account(
entity: entity,
name: "Cash",
balanceSheetRole: .cashAndEquivalents,
timeSeries: TimeSeries(periods: periods, values: [500_000, 600_000, 750_000, 900_000])
)
let receivables = try Account(
entity: entity,
name: "Receivables",
balanceSheetRole: .accountsReceivable,
timeSeries: TimeSeries(periods: periods, values: [300_000, 330_000, 360_000, 390_000])
)
let ppe = try Account(
entity: entity,
name: "PP&E",
balanceSheetRole: .propertyPlantEquipment,
timeSeries: TimeSeries(periods: periods, values: [1_000_000, 980_000, 960_000, 940_000])
)
let payables = try Account(
entity: entity,
name: "Payables",
balanceSheetRole: .accountsPayable,
timeSeries: TimeSeries(periods: periods, values: [200_000, 220_000, 240_000, 260_000])
)
let debt = try Account(
entity: entity,
name: "Long-Term Debt",
balanceSheetRole: .longTermDebtNoncurrent,
timeSeries: TimeSeries(periods: periods, values: [500_000, 500_000, 500_000, 500_000])
)
let equity = try Account(
entity: entity,
name: "Equity",
balanceSheetRole: .retainedEarnings,
timeSeries: TimeSeries(periods: periods, values: [1_100_000, 1_190_000, 1_330_000, 1_470_000])
)
let balanceSheet = try BalanceSheet(
entity: entity,
periods: periods,
accounts: [cash, receivables, ppe, payables, debt, equity]
)
Track business drivers that explain the financials:
// Define operational metrics for each quarter
let q1Metrics = OperationalMetrics(
entity: entity,
period: periods[0],
metrics: [
"units_sold": 10_000,
"average_price": 100.0,
"customer_count": 500,
"average_revenue_per_customer": 2_000
]
)
let q2Metrics = OperationalMetrics(
entity: entity,
period: periods[1],
metrics: [
"units_sold": 11_000,
"average_price": 100.0,
"customer_count": 550,
"average_revenue_per_customer": 2_000
]
)
let q3Metrics = OperationalMetrics(
entity: entity,
period: periods[2],
metrics: [
"units_sold": 12_000,
"average_price": 100.0,
"customer_count": 600,
"average_revenue_per_customer": 2_000
]
)
let q4Metrics = OperationalMetrics(
entity: entity,
period: periods[3],
metrics: [
"units_sold": 13_000,
"average_price": 100.0,
"customer_count": 650,
"average_revenue_per_customer": 2_000
]
)
let operationalMetrics = [q1Metrics, q2Metrics, q3Metrics, q4Metrics]
The insight: Operational metrics explain the financials. Revenue growth comes from adding 150 customers (30% increase) while maintaining price.
Combine statements and metrics into a comprehensive one-pager:
let q1Summary = try FinancialPeriodSummary(
entity: entity,
period: periods[0],
incomeStatement: incomeStatement,
balanceSheet: balanceSheet,
operationalMetrics: q1Metrics
)
print("=== Q1 2025 Financial Summary ===\n")
print("Revenue: \(q1Summary.revenue.currency())")
print("Gross Profit: \(q1Summary.grossProfit.currency())")
print("EBITDA: \(q1Summary.ebitda.currency())")
print("EBIT: \(q1Summary.operatingIncome.currency())")
print("Net Income: \(q1Summary.netIncome.currency())")
print()
print("Margins:")
print(" Gross Margin: \(q1Summary.grossMargin.percent(1))")
print(" Operating Margin: \(q1Summary.operatingMargin.percent(1))")
print(" Net Margin: \(q1Summary.netMargin.percent(1))")
print()
print("Returns:")
print(" ROA: \(q1Summary.roa.percent(1))")
print(" ROE: \(q1Summary.roe.percent(1))")
print()
print("Leverage:")
print(" Debt/Equity: \(q1Summary.debtToEquityRatio.number(2))x")
print(" Debt/EBITDA: \(q1Summary.debtToEBITDARatio.number(2))x")
print(" EBIT Interest Coverage: \(q1Summary.interestCoverageRatio!.number(1))x")
print()
print("Liquidity:")
print(" Current Ratio: \(q1Summary.currentRatio.number(2))x")
Output:
=== Q1 2025 Financial Summary ===
Revenue: $1,000,000.00
Gross Profit: $600,000.00
EBITDA: $300,000.00
EBIT: $250,000.00
Net Income: $178,000.00
Margins:
Gross Margin: 60.0%
Operating Margin: 25.0%
Net Margin: 17.8%
Returns:
ROA: 9.9%
ROE: 16.2%
Leverage:
Debt/Equity: 0.45x
Debt/EBITDA: 1.67x
EBIT Interest Coverage: 10.0x
Liquidity:
Current Ratio: 4.00x
The power: One FinancialPeriodSummary object gives you ~30 key metrics automatically computed.
Aggregate multiple periods for trend analysis:
// Create summaries for all quarters
let summaries = try periods.indices.map { index in
try FinancialPeriodSummary(
entity: entity,
period: periods[index],
incomeStatement: incomeStatement,
balanceSheet: balanceSheet,
operationalMetrics: operationalMetrics[index]
)
}
// Create multi-period report
let report = try MultiPeriodReport(
entity: entity,
periodSummaries: summaries
)
print("\n=== Acme Corporation - FY2025 Trends ===\n")
print("Periods analyzed: \(report.periodCount)")
Calculate period-over-period growth:
// Revenue growth
let revenueGrowth = report.revenueGrowth()
print("\nRevenue Growth (Q-o-Q):")
for (index, growth) in revenueGrowth.enumerated() {
let quarter = index + 2 // Q2, Q3, Q4
print(" Q\(quarter): \(growth.percent(1))")
}
// EBITDA growth
let ebitdaGrowth = report.ebitdaGrowth()
print("\nEBITDA Growth (Q-o-Q):")
for (index, growth) in ebitdaGrowth.enumerated() {
let quarter = index + 2
print(" Q\(quarter): \(growth.percent(1))")
}
// Net income growth
let netIncomeGrowth = report.netIncomeGrowth()
print("\nNet Income Growth (Q-o-Q):")
for (index, growth) in netIncomeGrowth.enumerated() {
let quarter = index + 2
print(" Q\(quarter): \(growth.percent(1))")
}
Output:
Periods analyzed: 4
Revenue Growth (Q-o-Q):
Q2: 10.0%
Q3: 9.1%
Q4: 8.3%
EBITDA Growth (Q-o-Q):
Q2: 8.3%
Q3: 13.8%
Q4: 9.5%
Net Income Growth (Q-o-Q):
Q2: 12.9%
Q3: 16.4%
Q4: 12.0%
The insight: Revenue growth is decelerating (10% → 9.1% → 8.3%), but net income growth is accelerating due to margin expansion.
Analyze margin evolution:
// Margin trends
let grossMargins = report.grossMarginTrend()
let operatingMargins = report.operatingMarginTrend()
let netMargins = report.netMarginTrend()
print("\n=== Margin Trend Analysis ===")
print("Period\t\tGross\tOperating\t Net")
print("------\t\t-----\t---------\t-------")
for i in 0...(periods.count - 1) {
let quarter = i + 1
print("Q\(quarter)\(grossMargins[i].percent(1).paddingLeft(toLength: 15))\(operatingMargins[i].percent(1).paddingLeft(toLength: 12))\(netMargins[i].percent(1).paddingLeft(toLength: 10))")
}
// Calculate margin expansion (convert from decimal to basis points)
// 1 percentage point = 100 basis points, so multiply decimal by 10,000
let grossExpansion = (grossMargins[3] - grossMargins[0]) * 10000
let operatingExpansion = (operatingMargins[3] - operatingMargins[0]) * 10000
let netExpansion = (netMargins[3] - netMargins[0]) * 10000
print("\nMargin Expansion (Q1 → Q4):")
print(" Gross: \(grossExpansion.number(0)) bps")
print(" Operating: \(operatingExpansion.number(0)) bps")
print(" Net: \(netExpansion.number(0)) bps")
Output:
=== Margin Trend Analysis ===
Period Gross Operating Net
------ ----- --------- -------
Q1 60.0% 25.0% 17.8%
Q2 59.1% 25.0% 18.3%
Q3 60.0% 26.7% 19.5%
Q4 60.0% 27.3% 20.2%
Margin Expansion (Q1 → Q4):
Gross: 0 bps
Operating: 231 bps
Net: 235 bps
The insight: Gross margin stable at 60%, while operating margin expanded 231 basis points (2.3 percentage points) and net margin expanded 235 basis points (2.4 percentage points) due to operating leverage and improving efficiency.
import BusinessMath
let entity = Entity(
id: "ACME",
primaryType: .ticker,
name: "Acme Corporation"
)
let periods = (1...4).map { Period.quarter(year: 2025, quarter: $0) }
// Revenue account
let revenue = try Account(
entity: entity,
name: "Product Revenue",
incomeStatementRole: .revenue,
timeSeries: TimeSeries(periods: periods, values: [1_000_000, 1_100_000, 1_200_000, 1_300_000])
)
// Expense accounts
let cogs = try Account(
entity: entity,
name: "Cost of Goods Sold",
incomeStatementRole: .costOfGoodsSold,
timeSeries: TimeSeries(periods: periods, values: [400_000, 450_000, 480_000, 520_000])
)
let opex = try Account(
entity: entity,
name: "Operating Expenses",
incomeStatementRole: .operatingExpenseOther,
timeSeries: TimeSeries(periods: periods, values: [300_000, 325_000, 350_000, 375_000])
)
let depreciation = try Account(
entity: entity,
name: "Depreciation",
incomeStatementRole: .depreciationAmortization,
timeSeries: TimeSeries(periods: periods, values: [50_000, 50_000, 50_000, 50_000])
)
let interest = try Account(
entity: entity,
name: "Interest Expense",
incomeStatementRole: .interestExpense,
timeSeries: TimeSeries(periods: periods, values: [25_000, 25_000, 25_000, 25_000])
)
let tax = try Account(
entity: entity,
name: "Income Tax",
incomeStatementRole: .incomeTaxExpense,
timeSeries: TimeSeries(periods: periods, values: [47_000, 49_000, 61_000, 68_000])
)
// Create Income Statement
let incomeStatement = try IncomeStatement(
entity: entity,
periods: periods,
accounts: [revenue, cogs, opex, depreciation, interest, tax]
)
// Create Balance Sheet (assets, liabilities, equity)
let cash = try Account(
entity: entity,
name: "Cash",
balanceSheetRole: .cashAndEquivalents,
timeSeries: TimeSeries(periods: periods, values: [500_000, 600_000, 750_000, 900_000])
)
let receivables = try Account(
entity: entity,
name: "Receivables",
balanceSheetRole: .accountsReceivable,
timeSeries: TimeSeries(periods: periods, values: [300_000, 330_000, 360_000, 390_000])
)
let ppe = try Account(
entity: entity,
name: "PP&E",
balanceSheetRole: .propertyPlantEquipment,
timeSeries: TimeSeries(periods: periods, values: [1_000_000, 980_000, 960_000, 940_000])
)
let payables = try Account(
entity: entity,
name: "Payables",
balanceSheetRole: .accountsPayable,
timeSeries: TimeSeries(periods: periods, values: [200_000, 220_000, 240_000, 260_000])
)
let debt = try Account(
entity: entity,
name: "Long-Term Debt",
balanceSheetRole: .longTermDebt,
timeSeries: TimeSeries(periods: periods, values: [500_000, 500_000, 500_000, 500_000])
)
let equity = try Account(
entity: entity,
name: "Equity",
balanceSheetRole: .retainedEarnings,
timeSeries: TimeSeries(periods: periods, values: [1_100_000, 1_190_000, 1_330_000, 1_470_000])
)
let balanceSheet = try BalanceSheet(
entity: entity,
periods: periods,
accounts: [cash, receivables, ppe, payables, debt, equity]
)
// Define operational metrics for each quarter
let q1Metrics = OperationalMetrics(
entity: entity,
period: periods[0],
metrics: [
"units_sold": 10_000,
"average_price": 100.0,
"customer_count": 500,
"average_revenue_per_customer": 2_000
]
)
let q2Metrics = OperationalMetrics(
entity: entity,
period: periods[1],
metrics: [
"units_sold": 11_000,
"average_price": 100.0,
"customer_count": 550,
"average_revenue_per_customer": 2_000
]
)
let q3Metrics = OperationalMetrics(
entity: entity,
period: periods[2],
metrics: [
"units_sold": 12_000,
"average_price": 100.0,
"customer_count": 600,
"average_revenue_per_customer": 2_000
]
)
let q4Metrics = OperationalMetrics(
entity: entity,
period: periods[3],
metrics: [
"units_sold": 13_000,
"average_price": 100.0,
"customer_count": 650,
"average_revenue_per_customer": 2_000
]
)
let operationalMetrics = [q1Metrics, q2Metrics, q3Metrics, q4Metrics]
let q1Summary = try FinancialPeriodSummary(
entity: entity,
period: periods[0],
incomeStatement: incomeStatement,
balanceSheet: balanceSheet,
operationalMetrics: q1Metrics
)
print("=== Q1 2025 Financial Summary ===\n")
print("Revenue: \(q1Summary.revenue.currency())")
print("Gross Profit: \(q1Summary.grossProfit.currency())")
print("EBITDA: \(q1Summary.ebitda.currency())")
print("EBIT: \(q1Summary.operatingIncome.currency())")
print("Net Income: \(q1Summary.netIncome.currency())")
print()
print("Margins:")
print(" Gross Margin: \(q1Summary.grossMargin.percent(1))")
print(" Operating Margin: \(q1Summary.operatingMargin.percent(1))")
print(" Net Margin: \(q1Summary.netMargin.percent(1))")
print()
print("Returns:")
print(" ROA: \(q1Summary.roa.percent(1))")
print(" ROE: \(q1Summary.roe.percent(1))")
print()
print("Leverage:")
print(" Debt/Equity: \(q1Summary.debtToEquityRatio.number(2))x")
print(" Debt/EBITDA: \(q1Summary.debtToEBITDARatio.number(2))x")
print(" EBIT Interest Coverage: \(q1Summary.interestCoverageRatio!.number(1))x")
print()
print("Liquidity:")
print(" Current Ratio: \(q1Summary.currentRatio.number(2))x")
// Create summaries for all quarters
let summaries = try periods.indices.map { index in
try FinancialPeriodSummary(
entity: entity,
period: periods[index],
incomeStatement: incomeStatement,
balanceSheet: balanceSheet,
operationalMetrics: operationalMetrics[index]
)
}
// Create multi-period report
let report = try MultiPeriodReport(
entity: entity,
periodSummaries: summaries
)
print("\n=== Acme Corporation - FY2025 Trends ===\n")
print("Periods analyzed: \(report.periodCount)")
// Revenue growth
let revenueGrowth = report.revenueGrowth()
print("\nRevenue Growth (Q-o-Q):")
for (index, growth) in revenueGrowth.enumerated() {
let quarter = index + 2 // Q2, Q3, Q4
print(" Q\(quarter): \(growth.percent(1))")
}
// EBITDA growth
let ebitdaGrowth = report.ebitdaGrowth()
print("\nEBITDA Growth (Q-o-Q):")
for (index, growth) in ebitdaGrowth.enumerated() {
let quarter = index + 2
print(" Q\(quarter): \(growth.percent(1))")
}
// Net income growth
let netIncomeGrowth = report.netIncomeGrowth()
print("\nNet Income Growth (Q-o-Q):")
for (index, growth) in netIncomeGrowth.enumerated() {
let quarter = index + 2
print(" Q\(quarter): \(growth.percent(1))")
}
// Margin trends
let grossMargins = report.grossMarginTrend()
let operatingMargins = report.operatingMarginTrend()
let netMargins = report.netMarginTrend()
print("\n=== Margin Trend Analysis ===")
print("Period\t\tGross\tOperating\t Net")
print("------\t\t-----\t---------\t-------")
for i in 0...(periods.count - 1) {
let quarter = i + 1
print("Q\(quarter)\(grossMargins[i].percent(1).paddingLeft(toLength: 15))\(operatingMargins[i].percent(1).paddingLeft(toLength: 12))\(netMargins[i].percent(1).paddingLeft(toLength: 10))")
}
// Calculate margin expansion (convert from decimal to basis points)
// 1 percentage point = 100 basis points, so multiply decimal by 10,000
let grossExpansion = (grossMargins[3] - grossMargins[0]) * 10000
let operatingExpansion = (operatingMargins[3] - operatingMargins[0]) * 10000
let netExpansion = (netMargins[3] - netMargins[0]) * 10000
print("\nMargin Expansion (Q1 → Q4):")
print(" Gross: \(grossExpansion.number(0)) bps")
print(" Operating: \(operatingExpansion.number(0)) bps")
print(" Net: \(netExpansion.number(0)) bps")
→ Full API Reference: BusinessMath Docs – 3.4 Financial Reports
Modifications to try:
This is how equity analysts create quarterly reports:
BusinessMath makes creating these reports programmatic and reproducible.
★ Insight ─────────────────────────────────────
Why Separate Financial Statements from Reports?
IncomeStatement and BalanceSheet model the raw data.
FinancialPeriodSummary computes derived metrics (EBITDA, ROE, ratios).
MultiPeriodReport analyzes trends (growth rates, margin expansion).
This separation follows the Single Responsibility Principle:
Each layer adds value without bloating the lower layers.
─────────────────────────────────────────────────
The hardest design decision was: How opinionated should the report format be?
We could have made FinancialPeriodSummary produce formatted output (tables, charts). But formatting requirements vary wildly:
We chose data-only output: FinancialPeriodSummary computes metrics and returns them as properties. You format however you want.
This makes the API flexible at the cost of requiring formatting code. Worth it.
Related Methodology: Documentation as Design (Week 2) - Designing APIs that users understand
Coming up next: Financial Statements Guide (Wednesday) - Deep dive into Income Statement, Balance Sheet, and Cash Flow Statement.
This week: Lease Accounting (Friday) - IFRS 16 / ASC 842 lease modeling.
Series Progress:
Tagged with: businessmath, swift, financial-reports, financial-statements, metrics