BusinessMath Quarterly Series
11 min read
Part 8 of 12-Week BusinessMath Series
Risk management requires quantifying uncertainty. What’s the worst loss we might face? How would a recession affect our portfolio? Are we properly diversified?
Traditional risk analysis involves complex calculations:
Implementing these correctly requires statistical knowledge, careful handling of distributions, and proper correlation modeling. You need production-ready risk analytics without reinventing the math.
BusinessMath provides comprehensive risk analytics including stress testing, VaR calculation, and multi-portfolio risk aggregation.
Evaluate how portfolios perform under adverse scenarios:
import BusinessMath
// Pre-defined stress scenarios
var allScenarios = [
StressScenario.recession, // Moderate economic downturn
StressScenario.crisis, // Severe financial crisis
StressScenario.supplyShock // Supply chain disruption
]
// Examine scenario parameters
for scenario in scenarios {
print("\(scenario.name):")
print(" Description: \(scenario.description)")
print(" Shocks:")
for (driver, shock) in scenario.shocks {
let pct = shock * 100
print(" \(driver): \(pct > 0 ? "+" : "")\(pct)%")
}
}
Output:
Recession:
Description: Economic recession scenario
Shocks:
Revenue: -15.0%
COGS: +5.0%
InterestRate: +2.0%
Financial Crisis:
Description: Severe financial crisis (2008-style)
Shocks:
Revenue: -30.0%
InterestRate: +5.0%
CustomerChurn: +20.0%
COGS: +10.0%
Supply Chain Shock:
Description: Major supply chain disruption
Shocks:
InventoryLevel: -30.0%
DeliveryTime: +50.0%
COGS: +25.0%
Create scenarios specific to your business:
// Pandemic scenario
let pandemic = StressScenario(
name: "Global Pandemic",
description: "Extended lockdowns and remote work transition",
shocks: [
"Revenue": -0.35, // -35% revenue
"RemoteWorkCosts": 0.20, // +20% IT/remote costs
"TravelExpenses": -0.80, // -80% travel
"RealEstateCosts": -0.15 // -15% office costs
]
)
allScenarios.append(pandemic)
// Regulatory change scenario
let regulation = StressScenario(
name: "New Regulation",
description: "Stricter compliance requirements",
shocks: [
"ComplianceCosts": 0.50, // +50% compliance
"Revenue": -0.05, // -5% from restrictions
"OperatingMargin": -0.03 // -3% margin compression
]
)
allScenarios.append(regulation)
Apply scenarios to your financial model:
let stressTest = StressTest(scenarios: allScenarios)
struct FinancialMetrics {
var revenue: Double
var costs: Double
var npv: Double
}
let baseline = FinancialMetrics(
revenue: 10_000_000,
costs: 7_000_000,
npv: 5_000_000
)
for scenario in stressTest.scenarios {
// Apply shocks
var stressed = baseline
if let revenueShock = scenario.shocks["Revenue"] {
stressed.revenue *= (1 + revenueShock)
}
if let cogsShock = scenario.shocks["COGS"] {
stressed.costs *= (1 + cogsShock)
}
let stressedNPV = stressed.revenue - stressed.costs
let impact = stressedNPV - baseline.npv
let impactPct = (impact / baseline.npv)
print("\n\(scenario.name):")
print(" Baseline NPV: \(baseline.npv.currency())")
print(" Stressed NPV: \(stressedNPV.currency())")
print(" Impact: \(impact.currency()) (\(impactPct.percent()))")
}
VaR measures the maximum loss expected over a time horizon at a given confidence level.
// Portfolio returns (historical daily returns)
let spReturns: [Double] = [0.0088, 0.0079, -0.0116…] //(See file for data)
let periods = (0...(spReturns.count - 1)).map {
Period.day(Date().addingTimeInterval(Double($0) * 86400))
}
let timeSeries = TimeSeries(periods: periods, values: spReturns)
let riskMetrics = ComprehensiveRiskMetrics(
returns: timeSeries,
riskFreeRate: 0.02 / 250 // 2% annual = 0.008% daily
)
print("Value at Risk:")
print(" 95% VaR: \(riskMetrics.var95.percent())")
print(" 99% VaR: \(riskMetrics.var99.percent())")
// Interpret: "95% confidence we won't lose more than X% in a day"
let portfolioValue = 1_000_000.0
let var95Loss = abs(riskMetrics.var95) * portfolioValue
print("\nFor \(portfolioValue.currency(0)) portfolio:")
print(" 95% 1-day VaR: \(var95Loss.currency())")
print(" Meaning: 95% confident daily loss won't exceed \(var95Loss.currency())")
CVaR measures the average loss in the worst cases (beyond VaR):
print("\nConditional VaR (Expected Shortfall):")
print(" CVaR (95%): \(riskMetrics.cvar95.percent())")
print(" Tail Risk Ratio: \(riskMetrics.tailRisk.number())")
// CVaR is the expected loss if we're in the worst 5%
let cvarLoss = abs(riskMetrics.cvar95) * portfolioValue
print(" If in worst 5% of days, expect to lose: \(cvarLoss.currency())")
CVaR is better than VaR because it captures tail risk—the average loss when things go really bad, not just the threshold.
Get a complete risk profile:
print("\nComprehensive Risk Profile:")
print(riskMetrics.description)
Output:
Comprehensive Risk Profile:
Comprehensive Risk Metrics:
VaR (95%): -1.66%
VaR (99%): -4.84%
CVaR (95%): -2.76%
Max Drawdown: 18.91%
Sharpe Ratio: 0.05
Sortino Ratio: 0.05
Tail Risk: 1.66
Skewness: 1.05
Kurtosis: 18.53
Maximum drawdown measures the largest peak-to-trough decline:
let drawdown = riskMetrics.maxDrawdown
print("\nDrawdown Analysis:")
print(" Maximum drawdown: \(drawdown.percent())")
if drawdown < 0.10 {
print(" Risk level: Low")
} else if drawdown < 0.20 {
print(" Risk level: Moderate")
} else {
print(" Risk level: High")
}
Risk-adjusted return measures:
print("\nRisk-Adjusted Returns:")
print(" Sharpe Ratio: \(riskMetrics.sharpeRatio.number(3))")
print(" (return per unit of total volatility)")
print(" Sortino Ratio: \(riskMetrics.sortinoRatio.number(3))")
print(" (return per unit of downside volatility)")
// Sortino > Sharpe indicates asymmetric returns (positive skew)
if riskMetrics.sortinoRatio > riskMetrics.sharpeRatio {
print(" Portfolio has limited downside with upside potential")
}
Sharpe Ratio penalizes all volatility (up and down).Sortino Ratio only penalizes downside volatility—better for assessing asymmetric strategies.
Skewness and kurtosis describe return distribution shape:
print("\nTail Statistics:")
print(" Skewness: \(riskMetrics.skewness)")
if riskMetrics.skewness < -0.5 {
print(" Negative skew: More frequent small gains, rare large losses")
print(" Risk: Fat left tail")
} else if riskMetrics.skewness > 0.5 {
print(" Positive skew: More frequent small losses, rare large gains")
print(" Risk: Fat right tail")
} else {
print(" Roughly symmetric distribution")
}
print(" Excess Kurtosis: \(riskMetrics.kurtosis)")
if riskMetrics.kurtosis > 1.0 {
print(" Fat tails: More extreme events than normal distribution")
print(" Risk: Higher probability of large moves")
}
Combine VaR across multiple portfolios accounting for correlations:
// Three portfolios with individual VaRs
let portfolioVaRs = [100_000.0, 150_000.0, 200_000.0]
// Correlation matrix
let correlations = [
[1.0, 0.6, 0.4],
[0.6, 1.0, 0.5],
[0.4, 0.5, 1.0]
]
// Aggregate VaR using variance-covariance method
let aggregatedVaR = RiskAggregator.aggregateVaR(
individualVaRs: portfolioVaRs,
correlations: correlations
)
let simpleSum = portfolioVaRs.reduce(0, +)
let diversificationBenefit = simpleSum - aggregatedVaR
print("VaR Aggregation:")
print(" Portfolio A VaR: \(portfolioVaRs[0].currency())")
print(" Portfolio B VaR: \(portfolioVaRs[1].currency())")
print(" Portfolio C VaR: \(portfolioVaRs[2].currency())")
print(" Simple sum: \(simpleSum.currency())")
print(" Aggregated VaR: \(aggregatedVaR.currency())")
print(" Diversification benefit: \(diversificationBenefit.currency())")
Diversification benefit shows how much risk is reduced by not being perfectly correlated.
Understand how much each portfolio contributes to total risk:
for i in 0...marginalVaR(
entity: i,
individualVaRs: portfolioVaRs,
correlations: correlations
)
print("\nPortfolio \(["A", "B", "C"][i]):")
print(" Individual VaR: \(portfolioVaRs[i].currency())")
print(" Marginal VaR: \(marginal.currency())")
print(" Risk contribution: \((marginal / aggregatedVaR).percent())")
}
Marginal VaR tells you: “If I added $1 more to this portfolio, how much would total VaR increase?”
S&P Returns Data (add to the /Sources file of your playground)
import BusinessMath
// Pre-defined stress scenarios
var allScenarios = [
StressScenario.recession, // Moderate economic downturn
StressScenario.crisis, // Severe financial crisis
StressScenario.supplyShock // Supply chain disruption
]
// Examine scenario parameters
for scenario in allScenarios {
print("\(scenario.name):")
print(" Description: \(scenario.description)")
print(" Shocks:")
for (driver, shock) in scenario.shocks {
let pct = shock * 100
print(" \(driver): \(pct > 0 ? "+" : "")\(pct)%")
}
}
// Pandemic scenario
let pandemic = StressScenario(
name: "Global Pandemic",
description: "Extended lockdowns and remote work transition",
shocks: [
"Revenue": -0.35, // -35% revenue
"RemoteWorkCosts": 0.20, // +20% IT/remote costs
"TravelExpenses": -0.80, // -80% travel
"RealEstateCosts": -0.15 // -15% office costs
]
)
allScenarios.append(pandemic)
// Regulatory change scenario
let regulation = StressScenario(
name: "New Regulation",
description: "Stricter compliance requirements",
shocks: [
"ComplianceCosts": 0.50, // +50% compliance
"Revenue": -0.05, // -5% from restrictions
"OperatingMargin": -0.03 // -3% margin compression
]
)
allScenarios.append(regulation)
let stressTest = StressTest(scenarios: allScenarios)
struct FinancialMetrics {
var revenue: Double
var costs: Double
var npv: Double
}
let baseline = FinancialMetrics(
revenue: 10_000_000,
costs: 7_000_000,
npv: 5_000_000
)
for scenario in stressTest.scenarios {
// Apply shocks
var stressed = baseline
if let revenueShock = scenario.shocks["Revenue"] {
stressed.revenue *= (1 + revenueShock)
}
if let cogsShock = scenario.shocks["COGS"] {
stressed.costs *= (1 + cogsShock)
}
let stressedNPV = stressed.revenue - stressed.costs
let impact = stressedNPV - baseline.npv
let impactPct = (impact / baseline.npv)
print("\n\(scenario.name):")
print(" Baseline NPV: \(baseline.npv.currency())")
print(" Stressed NPV: \(stressedNPV.currency())")
print(" Impact: \(impact.currency()) (\(impactPct.percent()))")
}
// Portfolio returns (historical daily returns) come from Sources: spReturns: [Double]
let periods: [Period] = (0.. = TimeSeries(periods: periods, values: spReturns)
let riskMetrics = ComprehensiveRiskMetrics(
returns: timeSeries,
riskFreeRate: 0.02 / 250 // 2% annual = 0.008% daily
)
print("Value at Risk:")
print(" 95% VaR: \(riskMetrics.var95.percent())")
print(" 99% VaR: \(riskMetrics.var99.percent())")
// Interpret: "95% confidence we won't lose more than X% in a day"
let portfolioValue = 1_000_000.0
let var95Loss = abs(riskMetrics.var95) * portfolioValue
print("\nFor \(portfolioValue.currency(0)) portfolio:")
print(" 95% 1-day VaR: \(var95Loss.currency())")
print(" Meaning: 95% confident daily loss won't exceed \(var95Loss.currency())")
print("\nConditional VaR (Expected Shortfall):")
print(" CVaR (95%): \(riskMetrics.cvar95.percent())")
print(" Tail Risk Ratio: \(riskMetrics.tailRisk.number())")
// CVaR is the expected loss if we're in the worst 5%
let cvarLoss = abs(riskMetrics.cvar95) * portfolioValue
print(" If in worst 5% of days, expect to lose: \(cvarLoss.currency())")
print("\nComprehensive Risk Profile:")
print(riskMetrics.description)
let drawdown = riskMetrics.maxDrawdown
print("\nDrawdown Analysis:")
print(" Maximum drawdown: \(drawdown.percent())")
if drawdown < 0.10 {
print(" Risk level: Low")
} else if drawdown < 0.20 {
print(" Risk level: Moderate")
} else {
print(" Risk level: High")
}
print("\nRisk-Adjusted Returns:")
print(" Sharpe Ratio: \(riskMetrics.sharpeRatio.number(3))")
print(" (return per unit of total volatility)")
print(" Sortino Ratio: \(riskMetrics.sortinoRatio.number(3))")
print(" (return per unit of downside volatility)")
// Sortino > Sharpe indicates asymmetric returns (positive skew)
if riskMetrics.sortinoRatio > riskMetrics.sharpeRatio {
print(" Portfolio has limited downside with upside potential")
}
print("\nTail Statistics:")
print(" Skewness: \(riskMetrics.skewness.number(2))")
if riskMetrics.skewness < -0.5 {
print(" Negative skew: More frequent small gains, rare large losses")
print(" Risk: Fat left tail")
} else if riskMetrics.skewness > 0.5 {
print(" Positive skew: More frequent small losses, rare large gains")
print(" Risk: Fat right tail")
} else {
print(" Roughly symmetric distribution")
}
print(" Excess Kurtosis: \(riskMetrics.kurtosis.number(2))")
if riskMetrics.kurtosis > 1.0 {
print(" Fat tails: More extreme events than normal distribution")
print(" Risk: Higher probability of large moves")
}
// Three portfolios with individual VaRs
let portfolioVaRs = [100_000.0, 150_000.0, 200_000.0]
// Correlation matrix
let correlations = [
[1.0, 0.6, 0.4],
[0.6, 1.0, 0.5],
[0.4, 0.5, 1.0]
]
// Aggregate VaR using variance-covariance method
let aggregatedVaR = RiskAggregator.aggregateVaR(
individualVaRs: portfolioVaRs,
correlations: correlations
)
let simpleSum = portfolioVaRs.reduce(0, +)
let diversificationBenefit = simpleSum - aggregatedVaR
print("VaR Aggregation:")
print(" Portfolio A VaR: \(portfolioVaRs[0].currency())")
print(" Portfolio B VaR: \(portfolioVaRs[1].currency())")
print(" Portfolio C VaR: \(portfolioVaRs[2].currency())")
print(" Simple sum: \(simpleSum.currency())")
print(" Aggregated VaR: \(aggregatedVaR.currency())")
print(" Diversification benefit: \(diversificationBenefit.currency())")
for i in 0...marginalVaR(
entity: i,
individualVaRs: portfolioVaRs,
correlations: correlations
)
print("\nPortfolio \(["A", "B", "C"][i]):")
print(" Individual VaR: \(portfolioVaRs[i].currency())")
print(" Marginal VaR: \(marginal.currency())")
print(" Risk contribution: \((marginal / aggregatedVaR).percent())")
}
→ Full API Reference: BusinessMath Docs – 2.3 Risk Analytics
Modifications to try:
Risk managers use these tools daily:
BusinessMath makes these institutional-grade analytics accessible in 10-20 lines of Swift code.
★ Insight ─────────────────────────────────────
Why Both VaR and CVaR?
VaR answers: “What’s the threshold of the worst 5% of outcomes?”CVaR answers: “When you’re in that worst 5%, how bad does it actually get?”
Example: Portfolio with VaR₉₅ = -$100k, CVaR₉₅ = -$500k
CVaR captures tail risk—the thing that kills portfolios. VaR alone can be misleading for fat-tailed distributions.
This distinction matters for crypto, options, and leveraged strategies where tails are fat.
─────────────────────────────────────────────────
The hardest part of implementing VaR wasn’t the math—it was choosing which variant to implement. There are three common methods:
We chose Historical VaR as the default because:
But we documented this choice explicitly in both code and DocC, so users know what they’re getting.
The lesson: When multiple valid implementations exist, pick one, document it clearly, and make the choice transparent.
Related Methodology: When Tests Pass But Code Is Wrong (Week 9) - Validating statistical implementations
Coming up this week: Week 3 explores operational models—growth, depreciation, and revenue modeling.
Case Study: Week 3 Friday combines depreciation + TVM for capital equipment purchase decisions.
Series Progress:
Tagged with: businessmath, swift, risk, var, stress-testing, portfolio