250 lines
7.0 KiB
Swift
250 lines
7.0 KiB
Swift
import Foundation
|
||
|
||
struct InvestmentMetrics {
|
||
// Basic Metrics
|
||
let totalValue: Decimal
|
||
let totalContributions: Decimal
|
||
let absoluteReturn: Decimal
|
||
let percentageReturn: Decimal
|
||
|
||
// Advanced Metrics
|
||
let cagr: Double // Compound Annual Growth Rate
|
||
let twr: Double // Time-Weighted Return
|
||
let volatility: Double // Standard deviation annualized
|
||
let maxDrawdown: Double // Maximum peak-to-trough decline
|
||
let sharpeRatio: Double // Risk-adjusted return
|
||
let bestMonth: MonthlyReturn?
|
||
let worstMonth: MonthlyReturn?
|
||
let winRate: Double // Percentage of positive months
|
||
let averageMonthlyReturn: Double
|
||
|
||
// Time period
|
||
let startDate: Date?
|
||
let endDate: Date?
|
||
let totalMonths: Int
|
||
|
||
struct MonthlyReturn: Identifiable {
|
||
let id = UUID()
|
||
let date: Date
|
||
let returnPercentage: Double
|
||
|
||
var formattedDate: String {
|
||
let formatter = DateFormatter()
|
||
formatter.dateFormat = "MMM yyyy"
|
||
return formatter.string(from: date)
|
||
}
|
||
|
||
var formattedReturn: String {
|
||
String(format: "%.1f%%", returnPercentage)
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - Formatting Helpers
|
||
|
||
extension InvestmentMetrics {
|
||
var formattedTotalValue: String {
|
||
formatCurrency(totalValue)
|
||
}
|
||
|
||
var formattedAbsoluteReturn: String {
|
||
let prefix = absoluteReturn >= 0 ? "+" : ""
|
||
return prefix + formatCurrency(absoluteReturn)
|
||
}
|
||
|
||
var formattedPercentageReturn: String {
|
||
let prefix = percentageReturn >= 0 ? "+" : ""
|
||
return String(format: "\(prefix)%.2f%%", NSDecimalNumber(decimal: percentageReturn).doubleValue)
|
||
}
|
||
|
||
var formattedCAGR: String {
|
||
String(format: "%.2f%%", cagr)
|
||
}
|
||
|
||
var formattedTWR: String {
|
||
String(format: "%.2f%%", twr)
|
||
}
|
||
|
||
var formattedVolatility: String {
|
||
String(format: "%.2f%%", volatility)
|
||
}
|
||
|
||
var formattedMaxDrawdown: String {
|
||
String(format: "%.2f%%", maxDrawdown)
|
||
}
|
||
|
||
var formattedSharpeRatio: String {
|
||
String(format: "%.2f", sharpeRatio)
|
||
}
|
||
|
||
var formattedWinRate: String {
|
||
String(format: "%.0f%%", winRate)
|
||
}
|
||
|
||
var formattedAverageMonthlyReturn: String {
|
||
let prefix = averageMonthlyReturn >= 0 ? "+" : ""
|
||
return String(format: "\(prefix)%.2f%%", averageMonthlyReturn)
|
||
}
|
||
|
||
private func formatCurrency(_ value: Decimal) -> String {
|
||
CurrencyFormatter.format(value, style: .currency, maximumFractionDigits: 2)
|
||
}
|
||
}
|
||
|
||
// MARK: - Empty State
|
||
|
||
extension InvestmentMetrics {
|
||
static var empty: InvestmentMetrics {
|
||
InvestmentMetrics(
|
||
totalValue: 0,
|
||
totalContributions: 0,
|
||
absoluteReturn: 0,
|
||
percentageReturn: 0,
|
||
cagr: 0,
|
||
twr: 0,
|
||
volatility: 0,
|
||
maxDrawdown: 0,
|
||
sharpeRatio: 0,
|
||
bestMonth: nil,
|
||
worstMonth: nil,
|
||
winRate: 0,
|
||
averageMonthlyReturn: 0,
|
||
startDate: nil,
|
||
endDate: nil,
|
||
totalMonths: 0
|
||
)
|
||
}
|
||
}
|
||
|
||
// MARK: - Category Metrics
|
||
|
||
struct CategoryMetrics: Identifiable {
|
||
let id: UUID
|
||
let categoryName: String
|
||
let colorHex: String
|
||
let icon: String
|
||
let totalValue: Decimal
|
||
let percentageOfPortfolio: Double
|
||
let metrics: InvestmentMetrics
|
||
|
||
var formattedTotalValue: String {
|
||
CurrencyFormatter.format(totalValue, style: .currency, maximumFractionDigits: 0)
|
||
}
|
||
|
||
var formattedPercentage: String {
|
||
String(format: "%.1f%%", percentageOfPortfolio)
|
||
}
|
||
}
|
||
|
||
// MARK: - Portfolio Summary
|
||
|
||
struct PortfolioSummary {
|
||
let totalValue: Decimal
|
||
let totalContributions: Decimal
|
||
let dayChange: Decimal
|
||
let dayChangePercentage: Double
|
||
let weekChange: Decimal
|
||
let weekChangePercentage: Double
|
||
let monthChange: Decimal
|
||
let monthChangePercentage: Double
|
||
let yearChange: Decimal
|
||
let yearChangePercentage: Double
|
||
let allTimeReturn: Decimal
|
||
let allTimeReturnPercentage: Double
|
||
let sourceCount: Int
|
||
let lastUpdated: Date?
|
||
|
||
var formattedTotalValue: String {
|
||
CurrencyFormatter.format(totalValue, style: .currency, maximumFractionDigits: 0)
|
||
}
|
||
|
||
var formattedDayChange: String {
|
||
formatChange(dayChange, dayChangePercentage)
|
||
}
|
||
|
||
var formattedMonthChange: String {
|
||
formatChange(monthChange, monthChangePercentage)
|
||
}
|
||
|
||
var formattedYearChange: String {
|
||
formatChange(yearChange, yearChangePercentage)
|
||
}
|
||
|
||
var formattedAllTimeReturn: String {
|
||
formatChange(allTimeReturn, allTimeReturnPercentage)
|
||
}
|
||
|
||
private func formatChange(_ absolute: Decimal, _ percentage: Double) -> String {
|
||
let prefix = absolute >= 0 ? "+" : ""
|
||
let absString = CurrencyFormatter.format(absolute, style: .currency, maximumFractionDigits: 0)
|
||
let pctString = String(format: "%.2f%%", percentage)
|
||
|
||
return "\(prefix)\(absString) (\(prefix)\(pctString))"
|
||
}
|
||
|
||
static var empty: PortfolioSummary {
|
||
PortfolioSummary(
|
||
totalValue: 0,
|
||
totalContributions: 0,
|
||
dayChange: 0,
|
||
dayChangePercentage: 0,
|
||
weekChange: 0,
|
||
weekChangePercentage: 0,
|
||
monthChange: 0,
|
||
monthChangePercentage: 0,
|
||
yearChange: 0,
|
||
yearChangePercentage: 0,
|
||
allTimeReturn: 0,
|
||
allTimeReturnPercentage: 0,
|
||
sourceCount: 0,
|
||
lastUpdated: nil
|
||
)
|
||
}
|
||
}
|
||
|
||
// MARK: - Portfolio Forecast
|
||
|
||
struct PortfolioForecast {
|
||
let currentValue: Decimal
|
||
let forecastValue: Decimal
|
||
let forecastDate: Date
|
||
let confidenceLower: Decimal
|
||
let confidenceUpper: Decimal
|
||
let monthlyGrowthRate: Decimal
|
||
let annualizedGrowthRate: Decimal
|
||
|
||
var formattedForecastValue: String {
|
||
CurrencyFormatter.format(forecastValue, style: .currency, maximumFractionDigits: 0)
|
||
}
|
||
|
||
var formattedCurrentValue: String {
|
||
CurrencyFormatter.format(currentValue, style: .currency, maximumFractionDigits: 0)
|
||
}
|
||
|
||
var formattedConfidenceRange: String {
|
||
let lower = CurrencyFormatter.format(confidenceLower, style: .currency, maximumFractionDigits: 0)
|
||
let upper = CurrencyFormatter.format(confidenceUpper, style: .currency, maximumFractionDigits: 0)
|
||
return "\(lower) – \(upper)"
|
||
}
|
||
|
||
var formattedForecastDate: String {
|
||
forecastDate.monthYearString
|
||
}
|
||
|
||
var formattedGrowthPercentage: String {
|
||
guard currentValue > 0 else { return "+0%" }
|
||
let growthPct = ((forecastValue - currentValue) / currentValue) * 100
|
||
let prefix = growthPct >= 0 ? "+" : ""
|
||
return String(format: "\(prefix)%.1f%%", NSDecimalNumber(decimal: growthPct).doubleValue)
|
||
}
|
||
|
||
var formattedAnnualizedGrowthRate: String {
|
||
let prefix = annualizedGrowthRate >= 0 ? "+" : ""
|
||
return String(format: "\(prefix)%.1f%%", NSDecimalNumber(decimal: annualizedGrowthRate * 100).doubleValue)
|
||
}
|
||
|
||
var isPositiveGrowth: Bool {
|
||
forecastValue >= currentValue
|
||
}
|
||
}
|