import SwiftUI import Charts struct DrawdownChart: View { let data: [(date: Date, drawdown: Double)] var maxDrawdown: Double { abs(data.map { $0.drawdown }.min() ?? 0) } var currentDrawdown: Double { abs(data.last?.drawdown ?? 0) } var body: some View { VStack(alignment: .leading, spacing: 16) { HStack { Text("Drawdown Analysis") .font(.headline) Spacer() VStack(alignment: .trailing, spacing: 2) { Text("Max Drawdown") .font(.caption) .foregroundColor(.secondary) Text(String(format: "%.1f%%", maxDrawdown)) .font(.subheadline.weight(.semibold)) .foregroundColor(.negativeRed) } } Text("Shows percentage decline from peak values") .font(.caption) .foregroundColor(.secondary) if data.count >= 2 { Chart(data, id: \.date) { item in AreaMark( x: .value("Date", item.date), y: .value("Drawdown", item.drawdown) ) .foregroundStyle( LinearGradient( colors: [Color.negativeRed.opacity(0.5), Color.negativeRed.opacity(0.1)], startPoint: .top, endPoint: .bottom ) ) .interpolationMethod(.catmullRom) LineMark( x: .value("Date", item.date), y: .value("Drawdown", item.drawdown) ) .foregroundStyle(Color.negativeRed) .interpolationMethod(.catmullRom) } .chartXAxis { AxisMarks(values: .stride(by: .month, count: 2)) { value in AxisValueLabel(format: .dateTime.month(.abbreviated)) } } .chartYAxis { AxisMarks { value in AxisGridLine() AxisValueLabel { if let doubleValue = value.as(Double.self) { Text(String(format: "%.0f%%", doubleValue)) .font(.caption) } } } } .chartYScale(domain: (data.map { $0.drawdown }.min() ?? -50)...0) .frame(height: 250) // Statistics HStack(spacing: 20) { DrawdownStatView( title: "Current", value: String(format: "%.1f%%", currentDrawdown), isHighlighted: currentDrawdown > maxDrawdown * 0.8 ) DrawdownStatView( title: "Maximum", value: String(format: "%.1f%%", maxDrawdown), isHighlighted: true ) DrawdownStatView( title: "Average", value: String(format: "%.1f%%", averageDrawdown), isHighlighted: false ) } .padding(.top, 8) } else { Text("Not enough data for drawdown analysis") .foregroundColor(.secondary) .frame(height: 250) .frame(maxWidth: .infinity) } } .padding() .background(Color(.systemBackground)) .cornerRadius(AppConstants.UI.cornerRadius) .shadow(color: .black.opacity(0.05), radius: 8, y: 2) } private var averageDrawdown: Double { guard !data.isEmpty else { return 0 } let sum = data.reduce(0.0) { $0 + abs($1.drawdown) } return sum / Double(data.count) } } struct DrawdownStatView: View { let title: String let value: String let isHighlighted: Bool var body: some View { VStack(spacing: 4) { Text(title) .font(.caption) .foregroundColor(.secondary) Text(value) .font(.subheadline.weight(.semibold)) .foregroundColor(isHighlighted ? .negativeRed : .primary) } .frame(maxWidth: .infinity) } } // MARK: - Volatility Chart View struct VolatilityChartView: View { let data: [(date: Date, volatility: Double)] var currentVolatility: Double { data.last?.volatility ?? 0 } var averageVolatility: Double { guard !data.isEmpty else { return 0 } return data.reduce(0.0) { $0 + $1.volatility } / Double(data.count) } var body: some View { VStack(alignment: .leading, spacing: 16) { HStack { Text("Volatility") .font(.headline) Spacer() VStack(alignment: .trailing, spacing: 2) { Text("Current") .font(.caption) .foregroundColor(.secondary) Text(String(format: "%.1f%%", currentVolatility)) .font(.subheadline.weight(.semibold)) .foregroundColor(volatilityColor(currentVolatility)) } } Text("Measures price variability over time") .font(.caption) .foregroundColor(.secondary) if data.count >= 2 { Chart(data, id: \.date) { item in LineMark( x: .value("Date", item.date), y: .value("Volatility", item.volatility) ) .foregroundStyle(Color.appPrimary) .interpolationMethod(.catmullRom) AreaMark( x: .value("Date", item.date), y: .value("Volatility", item.volatility) ) .foregroundStyle( LinearGradient( colors: [Color.appPrimary.opacity(0.3), Color.appPrimary.opacity(0.0)], startPoint: .top, endPoint: .bottom ) ) .interpolationMethod(.catmullRom) // Average line RuleMark(y: .value("Average", averageVolatility)) .foregroundStyle(Color.gray.opacity(0.5)) .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 5])) .annotation(position: .top, alignment: .leading) { Text("Avg: \(String(format: "%.1f%%", averageVolatility))") .font(.caption2) .foregroundColor(.secondary) } } .chartXAxis { AxisMarks(values: .stride(by: .month, count: 2)) { value in AxisValueLabel(format: .dateTime.month(.abbreviated)) } } .chartYAxis { AxisMarks { value in AxisGridLine() AxisValueLabel { if let doubleValue = value.as(Double.self) { Text(String(format: "%.0f%%", doubleValue)) .font(.caption) } } } } .frame(height: 250) // Volatility interpretation HStack(spacing: 16) { VolatilityLevelView(level: "Low", range: "0-10%", color: .positiveGreen) VolatilityLevelView(level: "Medium", range: "10-20%", color: .appWarning) VolatilityLevelView(level: "High", range: "20%+", color: .negativeRed) } .padding(.top, 8) } else { Text("Not enough data for volatility analysis") .foregroundColor(.secondary) .frame(height: 250) .frame(maxWidth: .infinity) } } .padding() .background(Color(.systemBackground)) .cornerRadius(AppConstants.UI.cornerRadius) .shadow(color: .black.opacity(0.05), radius: 8, y: 2) } private func volatilityColor(_ volatility: Double) -> Color { switch volatility { case 0..<10: return .positiveGreen case 10..<20: return .appWarning default: return .negativeRed } } } struct VolatilityLevelView: View { let level: String let range: String let color: Color var body: some View { HStack(spacing: 4) { Circle() .fill(color) .frame(width: 8, height: 8) VStack(alignment: .leading, spacing: 0) { Text(level) .font(.caption2) Text(range) .font(.caption2) .foregroundColor(.secondary) } } } } #Preview { let drawdownData: [(date: Date, drawdown: Double)] = [ (Date().adding(months: -6), -5), (Date().adding(months: -5), -8), (Date().adding(months: -4), -3), (Date().adding(months: -3), -15), (Date().adding(months: -2), -10), (Date().adding(months: -1), -7), (Date(), -4) ] let volatilityData: [(date: Date, volatility: Double)] = [ (Date().adding(months: -6), 12), (Date().adding(months: -5), 15), (Date().adding(months: -4), 10), (Date().adding(months: -3), 22), (Date().adding(months: -2), 18), (Date().adding(months: -1), 14), (Date(), 11) ] return VStack(spacing: 20) { DrawdownChart(data: drawdownData) VolatilityChartView(data: volatilityData) } .padding() }