Simulador SAC vs PRICE: Comparativo com Custo de Oportunidade | O Estrategista Imobiliário
simulador sac vs price, comparar financiamento imobiliário, tabela sac ou price, custo de oportunidade financiamento, simulador equity imobiliário
Simulador de Financiamento: SAC vs PRICE
Esta ferramenta foi desenvolvida para ajudar você a decidir entre os dois principais sistemas de amortização do Brasil. Diferente dos simuladores bancários, aqui você visualiza o impacto patrimonial e o custo de oportunidade de cada escolha.
O que comparar? Observe a aba de Equity para ver como seu patrimônio cresce em cada sistema e utilize a aba Invest. para entender se vale a pena investir a diferença das parcelas.
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 820
## file: app.py
#| standalone: true
#| viewerHeight: 820
from shiny import App, render, ui, reactive
import pandas as pd
import plotly.graph_objs as go
from shinywidgets import output_widget, render_widget
# --- MOTOR FINANCEIRO ---
def calcular_tabelas(valor_fin, cet_anual, prazo_anos):
prazo_meses = prazo_anos * 12
taxa_mensal = (1 + cet_anual)**(1/12) - 1
dados_sac = []
saldo_sac = valor_fin
amort_sac = valor_fin / prazo_meses
pago_acum_sac = 0
for m in range(1, prazo_meses + 1):
juros = saldo_sac * taxa_mensal
parcela = amort_sac + juros
saldo_sac -= amort_sac
pago_acum_sac += parcela
dados_sac.append({"Mes": m, "Ano": m/12, "Parcela": parcela, "Amort": amort_sac, "Juros": juros, "Saldo": max(0, saldo_sac), "Total_Pago": pago_acum_sac})
dados_price = []
saldo_price = valor_fin
parcela_price = valor_fin * (taxa_mensal * (1 + taxa_mensal)**prazo_meses) / ((1 + taxa_mensal)**prazo_meses - 1)
pago_acum_price = 0
for m in range(1, prazo_meses + 1):
juros = saldo_price * taxa_mensal
amort = parcela_price - juros
saldo_price -= amort
pago_acum_price += parcela_price
dados_price.append({"Mes": m, "Ano": m/12, "Parcela": parcela_price, "Amort": amort, "Juros": juros, "Saldo": max(0, saldo_price), "Total_Pago": pago_acum_price})
return pd.DataFrame(dados_sac), pd.DataFrame(dados_price)
# --- CONFIGURACAO VISUAL ---
ESTILO_LAYOUT_BASE = dict(
template="plotly_white",
font=dict(family="Arial, sans-serif", size=11), # Reduzi um pouco o padrão para mobile
margin=dict(l=50, r=20, t=40, b=50), # Margens mais enxutas
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=-0.3, xanchor="center", x=0.5), # Legenda embaixo ajuda no mobile
xaxis=dict(title="Anos", showline=True, linewidth=1, dtick=5),
yaxis=dict(showline=True, linewidth=1, tickformat=",.0f")
)
app_ui = ui.page_fluid(
ui.head_content(
# Meta tag para garantir que o mobile entenda o redimensionamento
ui.tags.meta(name="viewport", content="width=device-width, initial-scale=0.8, maximum-scale=1.0, user-scalable=yes"),
ui.tags.style("""
body { background-color: #f8f9fa; }
.insight-card {
background: white;
border-left: 5px solid #27ae60;
padding: 12px;
border-radius: 8px;
margin-bottom: 15px;
border: 1px solid #eee;
font-size: 0.9rem;
}
/* Ajuste para telas pequenas */
@media (max-width: 768px) {
h2 { font-size: 1.4rem !important; }
.insight-card { font-size: 0.8rem; padding: 8px; }
.control-panel .shiny-input-container { margin-bottom: 5px !important; }
}
""")
),
ui.markdown("## Estrategista Imobiliário: SAC vs PRICE"),
# Layout responsivo: 1 coluna no mobile, 4 no desktop
ui.layout_column_wrap(
ui.input_numeric("valor", "Valor (R$)", 500000, step=10000),
ui.input_slider("taxa", "CET (%)", 5.0, 20.0, 13.5, step=0.1),
ui.input_slider("selic", "Selic (%)", 5.0, 20.0, 14.65, step=0.05),
ui.input_slider("prazo", "Prazo (Anos)", 5, 35, 35),
width=1/2, # No mobile o Shiny tenta ajustar, mas o 'width' aqui define a base
widths_sm=1,
class_="control-panel"
),
ui.div(ui.output_ui("texto_insights"), class_="insight-card"),
ui.navset_tab(
ui.nav_panel("Parcelas", output_widget("grafico_parcelas")),
ui.nav_panel("Dívida", output_widget("grafico_saldo")),
ui.nav_panel("Despesa", output_widget("grafico_acumulado")),
ui.nav_panel("Invest.", output_widget("grafico_investimento")),
ui.nav_panel("Equity", output_widget("grafico_equity"))
)
)
def server(input, output, session):
@reactive.calc
def dados():
return calcular_tabelas(input.valor(), input.taxa()/100, input.prazo())
@output
@render.ui
def texto_insights():
df_sac, df_price = dados()
try:
idx_p = df_sac[df_sac['Parcela'] <= df_price['Parcela'].iloc[0]].index[0]
anos_p, meses_p = int(df_sac.iloc[idx_p]['Mes'] // 12), int(df_sac.iloc[idx_p]['Mes'] % 12)
ponto_c = df_price[df_price['Total_Pago'] > df_sac['Total_Pago']].iloc[0]
anos_c, meses_c = int(ponto_c['Mes'] // 12), int(ponto_c['Mes'] % 12)
economia = df_price['Total_Pago'].iloc[-1] - df_sac['Total_Pago'].iloc[-1]
return ui.markdown(f"""
* **Inversão de parcelas:** As parcelas SAC tornam-se mais econômicas que a PRICE após **{anos_p} anos e {meses_p} meses**.
* **Custo Total:** O sistema SAC torna-se mais vantajoso no valor acumulado após **{anos_c} anos e {meses_c} meses**.
* **Resultado Final:** A economia real do SAC ao quitar o contrato será de **R$ {economia:,.2f}**.
""")
except: return ui.markdown("Ajuste os filtros.")
@render_widget
def grafico_parcelas():
df_sac, df_price = dados()
fig = go.Figure()
fig.add_trace(go.Scatter(x=df_sac['Ano'], y=df_sac['Parcela'], name="SAC", line=dict(color='#0984e3', width=2)))
fig.add_trace(go.Scatter(x=df_sac['Ano'], y=df_sac['Juros'], name="Juros", line=dict(color='#0984e3', width=2, dash='dash')))
fig.add_trace(go.Scatter(x=df_price['Ano'], y=df_price['Parcela'], name="PRICE", line=dict(color='#d63031', width=2)))
fig.add_trace(go.Scatter(x=df_price['Ano'], y=df_price['Juros'], name="Juros", line=dict(color='#d63031', width=2, dash='dash')))
fig.update_layout(**ESTILO_LAYOUT_BASE, title="Parcela Mensal")
return fig
@render_widget
def grafico_saldo():
df_sac, df_price = dados()
fig = go.Figure()
fig.add_trace(go.Scatter(x=df_sac['Ano'], y=df_sac['Saldo'], name="SAC", line=dict(color='#0984e3', width=2)))
fig.add_trace(go.Scatter(x=df_price['Ano'], y=df_price['Saldo'], name="PRICE", line=dict(color='#d63031', width=2)))
fig.update_layout(**ESTILO_LAYOUT_BASE, title="Saldo Devedor")
return fig
@render_widget
def grafico_acumulado():
df_sac, df_price = dados()
fig = go.Figure()
fig.add_trace(go.Scatter(x=df_sac['Ano'], y=df_sac['Total_Pago'], name="Total SAC", line=dict(color='#0984e3')))
fig.add_trace(go.Scatter(x=df_price['Ano'], y=df_price['Total_Pago'], name="Total PRICE", line=dict(color='#d63031')))
fig.update_layout(**ESTILO_LAYOUT_BASE, title="Custo Total Pago")
return fig
@render_widget
def grafico_investimento():
df_sac, df_price = dados()
cdi_base, ir = input.selic() / 100, 0.15
percentuais = [1.0, 1.05, 1.10] # Reduzi para 3 curvas no mobile para não poluir
cores = ['#e74c3c', '#f1c40f', '#27ae60']
fig = go.Figure()
for pct, cor in zip(percentuais, cores):
taxa_anual = cdi_base * pct
taxa_m = (1 + taxa_anual)**(1/12) - 1
invest = [0]
for i in range(len(df_sac)):
diff = df_sac['Parcela'].iloc[i] - df_price['Parcela'].iloc[i]
saldo = invest[-1] * (1 + taxa_m * (1 - ir)) + diff
invest.append(max(0, saldo))
invest.pop(0)
fig.add_trace(go.Scatter(x=df_sac['Ano'], y=invest, name=f"{int(pct*100)}% CDI", line=dict(color=cor, width=2)))
fig.update_layout(**ESTILO_LAYOUT_BASE, title="Saldo de Oportunidade")
return fig
@render_widget
def grafico_equity():
df_sac, df_price = dados()
v_fin = input.valor()
cdi_base, ir = input.selic() / 100, 0.15
equity_sac = v_fin - df_sac['Saldo']
equity_price_puro = v_fin - df_price['Saldo']
fig = go.Figure()
fig.add_trace(go.Scatter(x=df_sac['Ano'], y=equity_sac, name="Equity SAC", line=dict(color='#0984e3', width=2)))
fig.add_trace(go.Scatter(x=df_price['Ano'], y=equity_price_puro, name="Equity PRICE", line=dict(color='#d63031', width=2)))
# Apenas 100% CDI para limpar o gráfico mobile
taxa_m = (1 + cdi_base)**(1/12) - 1
invest = [0]
divida = [0]
for i in range(len(df_sac)):
diff = df_sac['Parcela'].iloc[i] - df_price['Parcela'].iloc[i]
saldo = invest[-1] * (1 + taxa_m * (1 - ir)) + diff
invest.append(max(0, saldo))
divida.append(min(0, saldo))
invest.pop(0)
divida.pop(0)
# Eficiência de gerar patrimônio caso invista o dinheiro
eff = ((equity_price_puro+invest)/(df_sac['Total_Pago']-divida))
fig.add_trace(go.Scatter(x=df_sac['Ano'], y=equity_price_puro + invest, name="PRICE + 100% CDI", line=dict(color='#f1c40f', dash='dash')))
fig.update_layout(**ESTILO_LAYOUT_BASE, title="Evolução do Patrimônio")
return fig
app = App(app_ui, server)
## file: requirements.txt
pandas
plotly
shinywidgets
shiny
exceptiongroup; python_version < "3.11"
Guia de Decisão Rápida
Escolha SAC se: Você busca pagar menos juros no total e quer que sua dívida caia rápido desde o primeiro mês.
Escolha PRICE se: Você precisa de uma parcela inicial menor e tem disciplina para investir a diferença.
Para uma análise detalhada sobre os riscos e vantagens de cada tabela, leia nosso Artigo Completo sobre SAC vs PRICE.