Rediseño Charts iPad, zoom en gráficas, eliminación de Focus Mode y fixes de rendimiento
Charts iPad (rediseño): - Fila de KPIs sobre el chart: Total Value, Period Return, CAGR, Volatility y Max Drawdown del rango activo (nuevo ChartsViewModel.portfolioMetrics, calculado sobre los TOTALES MENSUALES AGREGADOS — pasar snapshots multi-source crudos a calculateMetrics producía KPIs absurdos) - Sidebar (248pt) con tiles de chart type en grid 2 col agrupados por sección: Overview / Analyze / Risk / Forecast, con candado premium y accesibilidad - Selector de periodo como Picker segmentado nativo en la cabecera del chart (fuera del sidebar); título + descripción del chart visibles - Eliminado sheet de paywall duplicado del layout iPad Zoom (todas las series temporales): - Nuevo ChartZoom.swift: pinch + botones +/- y reset (HIG: el gesto nunca es el único camino), chartScrollableAxes + chartXVisibleDomain al hacer zoom - Integrado en Evolution, Contributions, Rolling 12M, Cashflow, Drawdown y Volatility Focus Mode eliminado (todo siempre disponible): - Fuera el toggle de Dashboard/Charts/Settings/Onboarding y los 6 @AppStorage - Todos los chart types siempre visibles; variantes completas en SourceDetail/SourceList - Home: Total Portfolio Value muestra SIEMPRE el cambio desde el último check-in - PeriodReturnsCard siempre visible Rendimiento y bugs (de la auditoría): - El cache de snapshots ya no se invalida al cambiar solo de chart type/rango/breakdown - calculateAnnualizedGrowth y el forecast a 12 meses ahora componen (la aproximación lineal sobreestimaba con historiales cortos) - Drawdown sin force unwrap; simulador: rama muerta reemplazada por preservación real de las asignaciones simuladas del usuario - UITest de captura de Charts con manejo del alert de notificaciones 12 strings nuevas localizadas en los 7 idiomas. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
This commit is contained in:
@@ -494,8 +494,19 @@ class DashboardViewModel: ObservableObject {
|
||||
let totalGrowth = lastPoint.value - firstPoint.value
|
||||
let monthlyGrowth = totalGrowth / Decimal(monthsBetween)
|
||||
|
||||
// Project 12 months ahead
|
||||
let forecastValue = currentValue + (monthlyGrowth * 12)
|
||||
// Project 12 months ahead using compound growth (matches CAGR semantics);
|
||||
// fall back to the linear projection if the ratio is degenerate.
|
||||
let firstD = NSDecimalNumber(decimal: firstPoint.value).doubleValue
|
||||
let lastD = NSDecimalNumber(decimal: lastPoint.value).doubleValue
|
||||
let currentD = NSDecimalNumber(decimal: currentValue).doubleValue
|
||||
let forecastValue: Decimal
|
||||
if firstD > 0, lastD > 0 {
|
||||
let monthlyRate = pow(lastD / firstD, 1.0 / Double(monthsBetween))
|
||||
let projected = currentD * pow(monthlyRate, 12)
|
||||
forecastValue = projected.isFinite ? Decimal(projected) : currentValue + (monthlyGrowth * 12)
|
||||
} else {
|
||||
forecastValue = currentValue + (monthlyGrowth * 12)
|
||||
}
|
||||
|
||||
// Calculate confidence interval based on volatility
|
||||
let volatility = calculatePortfolioVolatility(from: values)
|
||||
@@ -537,10 +548,13 @@ class DashboardViewModel: ObservableObject {
|
||||
let monthsBetween = max(1, first.date.monthsBetween(last.date))
|
||||
let totalReturn = (last.value - first.value) / first.value
|
||||
|
||||
// Annualize: (1 + total_return)^(12/months) - 1
|
||||
let monthlyReturn = totalReturn / Decimal(monthsBetween)
|
||||
let annualized = monthlyReturn * 12
|
||||
return annualized
|
||||
// Annualize compounding: (1 + total_return)^(12/months) - 1.
|
||||
// The previous linear approximation (monthly*12) overstated short histories.
|
||||
let totalReturnD = NSDecimalNumber(decimal: totalReturn).doubleValue
|
||||
guard totalReturnD > -1 else { return 0 }
|
||||
let annualizedD = pow(1 + totalReturnD, 12.0 / Double(monthsBetween)) - 1
|
||||
guard annualizedD.isFinite else { return 0 }
|
||||
return Decimal(annualizedD)
|
||||
}
|
||||
|
||||
private func computeStreak(from evolutionData: [(date: Date, value: Decimal)]) -> Int {
|
||||
|
||||
Reference in New Issue
Block a user