Versão: 1.0
Data: Janeiro 2026
Status: Planejamento — Nenhuma implementação ainda
Desenhar um modelo de dados para preços históricos que permita:
model AssetPrice {
id Int @id @default(autoincrement())
assetId Int
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
price Decimal @db.Decimal(20, 8) // Suporta cripto e ações
currency String @default("USD")
source PriceSource @default(MANUAL)
recordedAt DateTime @default(now())
// Metadata opcional
exchange String? // Ex: "NYSE", "NASDAQ", "BINANCE"
volume Decimal? @db.Decimal(20, 8)
marketCap Decimal? @db.Decimal(20, 2)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([assetId, recordedAt])
@@index([recordedAt])
@@map("asset_prices")
}
enum PriceSource {
MANUAL // Preço inserido manualmente pelo usuário
YAHOO // Yahoo Finance API
POLYGON // Polygon.io API
ALPHA_VANTAGE // Alpha Vantage API
CRYPTO_COMPARE // CryptoCompare API
}
assetIdAssetpricecurrencysourcePriceSourcerecordedAtcreatedAt (quando foi inserido no banco)exchange, volume, marketCap@@index([assetId, recordedAt]) // Busca rápida de histórico por ativo
@@index([recordedAt]) // Busca rápida de preços por data
Justificativa:
WHERE assetId = X ORDER BY recordedAt DESC LIMIT 1WHERE recordedAt = DATEWHERE assetId = X ORDER BY recordedAtEstado Atual:
// Backend atual calcula marketValue baseado em lastPrice
marketValue = quantity * lastPrice
Estado Futuro:
// Backend buscará preço mais recente de AssetPrice
const latestPrice = await getLatestPrice(assetId);
marketValue = quantity * latestPrice.price;
Migração Incremental:
AssetPrice (nova, não quebra nada)lastPrice em Asset (backward compatibility)AssetPrice, usar; senão, usar lastPricelastPrice apenas quando 100% dos ativos tiverem preços reaisPrós:
Contras:
Uso Recomendado: MVP / Prototipagem
Prós:
Contras:
Uso Recomendado: Produção (quando houver budget)
Prós:
Contras:
Uso Recomendado: Produção pequena escala
Prós:
Contras:
Uso Recomendado: Complementar para crypto
┌─────────────────────────────────────────┐
│ Price Fetch Strategy │
└─────────────────────────────────────────┘
│
┌───────────┴───────────┐
│ │
┌────▼────┐ ┌────▼────┐
│ Stocks │ │ Crypto │
│ ETFs │ │ Tokens │
└────┬────┘ └────┬────┘
│ │
┌────▼────┐ ┌────▼────┐
│ Polygon │ │CoinGecko │
│ (paid) │ │ (free) │
└────┬────┘ └────┬────┘
│ │
└───────────┬───────────┘
│
┌───────▼───────┐
│ Fallback: │
│ Yahoo Finance│
└───────────────┘
┌─────────────────────────────────────────────┐
│ Cron Job (a cada hora / 4 horas) │
└─────────────────────────────────────────────┘
│
┌───────────┴───────────┐
│ │
┌────▼────┐ ┌────▼────┐
│ Fetch │ │ Fetch │
│ Stocks │ │ Crypto │
└────┬────┘ └────┬────┘
│ │
└───────────┬───────────┘
│
┌───────────▼───────────┐
│ Save to AssetPrice │
│ (batch insert) │
└───────────────────────┘
Vantagens:
Desvantagens:
┌─────────────────────────────────────────────┐
│ User clicks "Refresh Prices" │
└─────────────────────────────────────────────┘
│
┌───────────▼───────────┐
│ Get user's assets │
│ (from portfolios) │
└───────────┬───────────┘
│
┌───────────▼───────────┐
│ Fetch prices │
│ (rate limited) │
└───────────┬───────────┘
│
┌───────────▼───────────┐
│ Save to AssetPrice │
└───────────────────────┘
Vantagens:
Desvantagens:
┌─────────────────────────────────────────────┐
│ Cron: Atualiza preços principais │
│ (S&P 500, principais cryptos) │
└─────────────────────────────────────────────┘
│
┌───────────▼───────────┐
│ On-Demand: Ativos │
│ específicos do user │
└───────────────────────┘
// Pseudocódigo (não implementar ainda)
async function fetchPrice(asset: Asset): Promise<Price> {
// 1. Tentar fonte primária
try {
return await polygonApi.getPrice(asset.ticker);
} catch (error) {
// 2. Fallback para fonte secundária
try {
return await yahooFinance.getPrice(asset.ticker);
} catch (error) {
// 3. Se tudo falhar, usar último preço conhecido
return await getLastKnownPrice(asset.id);
}
}
}
// Pseudocódigo (não implementar ainda)
class PriceFetcher {
private rateLimiter: Map<string, number[]> = new Map();
async fetchWithRateLimit(source: string, ticker: string) {
const now = Date.now();
const calls = this.rateLimiter.get(source) || [];
// Remover calls antigas (última hora)
const recentCalls = calls.filter(time => now - time < 3600000);
if (recentCalls.length >= MAX_CALLS_PER_HOUR) {
throw new RateLimitError('Rate limit exceeded');
}
// Registrar call
recentCalls.push(now);
this.rateLimiter.set(source, recentCalls);
// Fazer fetch
return await this.fetchPrice(source, ticker);
}
}
backend/
├── src/
│ ├── assets/
│ │ ├── assets.service.ts (existente)
│ │ ├── assets.resolver.ts (existente)
│ │ └── assets.module.ts (existente)
│ │
│ ├── pricing/ (NOVO - não criar ainda)
│ │ ├── pricing.service.ts // Lógica de fetch
│ │ ├── pricing.resolver.ts // GraphQL queries
│ │ ├── pricing.module.ts
│ │ ├── providers/
│ │ │ ├── polygon.provider.ts
│ │ │ ├── yahoo.provider.ts
│ │ │ └── cryptocompare.provider.ts
│ │ └── jobs/
│ │ └── price-sync.job.ts // Cron job
│ │
│ └── portfolios/
│ └── portfolios.service.ts (modificar depois)
┌─────────────────────────────────────────────┐
│ Request: PortfolioSummary │
└─────────────────────────────────────────────┘
│
┌───────────▼───────────┐
│ Check cache │
│ (Redis / Memory) │
└───────────┬───────────┘
│
┌───────────┴───────────┐
│ │
┌────▼────┐ ┌────▼────┐
│ Cache │ │ Cache │
│ Hit │ │ Miss │
└────┬────┘ └────┬────┘
│ │
│ ┌────────▼────────┐
│ │ Fetch latest │
│ │ prices from DB │
│ └────────┬────────┘
│ │
└───────────┬───────────┘
│
┌───────────▼───────────┐
│ Calculate summary │
│ (with cached prices) │
└───────────────────────┘
Cache Strategy:
Definição: Soma do valor de mercado de todas as posições
Cálculo:
Portfolio Value = Σ(quantity × currentPrice)
Prioridade: 🔴 Alta
Complexidade: Baixa
Dependências: Price Snapshot (Fase 1)
Definição: Valor total investido (já implementado)
Cálculo:
Cost Basis = Σ(quantity × averageCost)
Prioridade: 🟢 Já implementado
Status: ✅ Completo
Definição: Diferença entre valor atual e custo
Cálculo:
Unrealized G/L = Portfolio Value - Cost Basis
Unrealized G/L % = (Unrealized G/L / Cost Basis) × 100
Prioridade: 🔴 Alta
Complexidade: Baixa
Dependências: Portfolio Value
Definição: Percentual de cada ativo/tipo no portfolio
Cálculo:
Allocation % = (Asset Value / Portfolio Value) × 100
Prioridade: 🟡 Média
Complexidade: Baixa
Status: ✅ Parcialmente implementado
Definição: Retorno ajustado por tempo, ignorando depósitos/saques
Cálculo:
TWR = [(1 + r1) × (1 + r2) × ... × (1 + rn)] - 1
Prioridade: 🟡 Média
Complexidade: Alta
Uso: Comparar performance de diferentes períodos
Definição: Retorno ajustado por valor investido (IRR)
Cálculo:
Resolve: NPV = 0
Onde: Σ(CFt / (1 + MWR)^t) = 0
Prioridade: 🟡 Média
Complexidade: Muito Alta
Uso: Performance real considerando timing de investimentos
Definição: Medida de variação dos retornos
Cálculo:
Volatility = √(Σ(ri - r̄)² / n)
Prioridade: 🟢 Baixa
Complexidade: Média
Uso: Medir risco do portfolio
Definição: Maior queda desde o pico
Cálculo:
Max Drawdown = (Peak - Trough) / Peak
Prioridade: 🟢 Baixa
Complexidade: Baixa
Uso: Entender pior cenário histórico
Definição: Retorno mensal do portfolio
Visualização:
Prioridade: 🟡 Média
Complexidade: Baixa
Definição: Retorno anual (YTD, 1Y, 3Y, 5Y)
Visualização:
Prioridade: 🟡 Média
Complexidade: Média
Definição: Performance relativa ao S&P 500 ou outro índice
Visualização:
Prioridade: 🟢 Baixa
Complexidade: Alta (requer dados de benchmark)
Fase 1 (MVP - 2-4 semanas):
├── Portfolio Value ✅
├── Cost Basis ✅
├── Unrealized G/L ✅
└── Allocation % ✅
Fase 2 (Intermediário - 1-2 meses):
├── Monthly Performance
├── Annual Performance
└── Volatility
Fase 3 (Avançado - 3+ meses):
├── Time-Weighted Return
├── Money-Weighted Return
├── Max Drawdown
└── Benchmark Comparison
Net Worth = Total Assets - Total Liabilities
No contexto do Wealth Tracker:
Net Worth =
Cash Balance +
Investment Portfolio Value +
Other Assets -
Recurring Expenses (projected) -
Other Liabilities
Fonte: Cashflow module
Cálculo:
Cash Balance =
Initial Balance +
Σ(Income entries) -
Σ(Expense entries) -
Σ(Recurring expenses projected)
Prioridade: 🔴 Alta
Status: Dados disponíveis no Cashflow
Fonte: Portfolio module
Cálculo:
Portfolio Value = Σ(quantity × currentPrice)
Prioridade: 🔴 Alta
Status: Depende de Price Snapshot (Fase 1)
Fonte: Cashflow Recurring
Cálculo:
Projected Expenses = Σ(monthlyRecurringAmount)
Prioridade: 🟡 Média
Uso: Mostrar compromissos futuros
Fonte: Novo módulo (futuro)
Exemplos:
Prioridade: 🟢 Baixa (futuro)
┌─────────────────────────────────────────────┐
│ NET WORTH DASHBOARD │
├─────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Cash │ │Portfolio │ │ Net │ │
│ │ $10,000 │ │ $50,000 │ │ $60,000 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ Net Worth Over Time (Chart) │ │
│ │ [Line chart: last 12 months] │ │
│ └───────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ Breakdown by Category │ │
│ │ [Pie chart: Cash vs Investments] │ │
│ └───────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ Recurring Expenses (Projected) │ │
│ │ - Netflix: $14.99/month │ │
│ │ - Rent: $1,200/month │ │
│ └───────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
Tipo: Line Chart
Eixo X: Meses (últimos 12)
Eixo Y: Valor em USD
Séries:
Tipo: Pie/Donut Chart
Categorias:
Tipo: Stacked Bar Chart
Eixo X: Meses
Eixo Y: Valor
Séries:
┌─────────────────┐
│ Cashflow │
│ - Entries │
│ - Recurring │
└────────┬────────┘
│
│ Cash Balance
│ Recurring Expenses
│
┌────────▼────────┐
│ Net Worth │
│ Calculator │
└────────┬────────┘
│
│ Portfolio Value
│
┌────────▼────────┐
│ Portfolio │
│ - Holdings │
│ - Prices │
└─────────────────┘
type NetWorthSummary {
totalNetWorth: Float!
cashBalance: Float!
portfolioValue: Float!
recurringExpensesMonthly: Float!
breakdown: NetWorthBreakdown!
history: [NetWorthPoint!]!
}
type NetWorthBreakdown {
cash: Float!
stocks: Float!
crypto: Float!
other: Float!
}
type NetWorthPoint {
date: DateTime!
total: Float!
cash: Float!
portfolio: Float!
}
Net Worth
├── Cash Balance (Cashflow) ✅ Existe
├── Portfolio Value (Portfolio + Prices) ⏳ Fase 1
└── Recurring Expenses (Cashflow) ✅ Existe
AssetPricelastPrice em Asset (compatibilidade)lastPrice se API falharPortfolioSummaryNetWorthSummary resolverMitigação:
Mitigação:
Mitigação:
Mitigação:
Este roadmap fornece uma base sólida para evoluir o Wealth Tracker de um sistema baseado em custo para um sistema completo de gestão de patrimônio com preços reais de mercado.
Próximos Passos:
Importante: Este documento é um plano. Nenhuma implementação deve ser feita sem aprovação explícita e sem seguir os princípios de evolução incremental descritos aqui.
Documento criado em: Janeiro 2026
Última atualização: Janeiro 2026
Status: Planejamento — Aguardando aprovação para implementação