Open JulioNegreiro opened 1 year ago
Obrigado por reportar esse erro, @JulioNegreiro.
Temporariamente removi a Selic e soltei uma nova versão do PyPI sem a Selic para evitar que pessoas usem a conversão “bugada”.
Acho que descobri onde está o erro: quantidade de casas decimais consideradas depois de todas as multiplicações acumuladas.
Se você faz a pesquisa por dia, ele devolve o fator acumulado com 16 casas decimais:
fator_diario_2017 = decimal.Decimal("1.0993932466542384")
Se você faz por mês, cada fator possui apenas 8 casas decimais:
Se multiplicarmos esses fatores mensais usando o decimal.Decimal
do Python acabamos com um número com 27 casas decimais:
fator_mensal_2017 = (
1
* decimal.Decimal("1.01086120")
* decimal.Decimal("1.00865084")
* decimal.Decimal("1.01052056")
* decimal.Decimal("1.00786581")
* decimal.Decimal("1.00927132")
* decimal.Decimal("1.00808869")
* decimal.Decimal("1.00797923")
* decimal.Decimal("1.00802289")
* decimal.Decimal("1.00638460")
* decimal.Decimal("1.00643930")
* decimal.Decimal("1.00568188")
* decimal.Decimal("1.00538400")
)
# resultado: Decimal('1.099393246654238379628934669')
Se arredondarmos o resultado para 16 dígitos os valores ficam iguais:
fator_mensal_2017_arredondado = fator_mensal_2017.quantize(decimal.Decimal("0.0000000000000001"))
fator_mensal_2017_arredondado == fator_diario_2017 # True
Então, adicionar um .quantize(decimal.Decimal("0.0000000000000001"))
no multiplicador final da Selic deve resolver (não cheguei a testar com o período completo citado, mas acho que dá pra criar um caso de teste com esses valores).
Vou levantar outra hipótese aqui, que é o do período de vigência e como o cálculo é ffeito.
No esse pacote, o fator acumulado dá 35,983673%, sendo que a do site é de 35,476542% — a diferença é de pouco mais de 0,5% — será que as datas de referência, e a forma como é calculado o acumúlo mensal dá conta dessa diferença?
Se sim, acho que temos que deixar claro essa questão de datas aqui:
Selic
para SelicMensal
(ou SelicAcumuladoMensal
) — esse seria algo de curto prazoSelic
com a precisão do prazo de vigência de cada taxa de juros — esse pode ser um objetivo mais de médio-prazoAcho que isso mais o cuidado com as casas decimais que o @turicas mencionou seria a solução mais definitiva. O que acham?
Eu tenho a impressão que a nossa chamada a API do BCB está muito fixada em meses fechados e isso não permite a gente comparar corretamente o dado, já que na teoria a gente estaria pegando a taxa referente ao mês inteiro de novembro também.
Só a critério de comparação, estou utilizando mais 1 site para vermos corretamente o que acontece, a referência de vários bancos e sistemas do mercado: Com Dinheiro e a taxa deles também dá um acumulado de 35,476542%
no período.
Para esse valor bater com um código simples que fiz utilizando a API do BCB com a série 11 (dados da Selic).
import pandas as pd
import requests
from datetime import datetime
from decimal import Decimal, getcontext
req = requests.get("https://api.bcb.gov.br/dados/serie/bcdata.sgs.11/dados?formato=json")
selic_json = req.json()
df_selic = pd.DataFrame(selic_json)
df_selic["data"] = pd.to_datetime(df_selic["data"])
df_selic["data"].iloc[0] # So para confirmar que convertemos o valor certo
# Utilizando Float
df_divergencia = df_selic[(df_selic["data"] >= datetime(2017, 10, 1)) & (df_selic["data"] <= datetime(2022, 11, 1))]
df_divergencia = df_divergencia.sort_values(by=['data'])
df_divergencia["valor"] = df_divergencia["valor"].apply(lambda x: 1 + (float(x.replace(",", ".")) / 100))
df_divergencia["valor"] = df_divergencia["valor"].cumprod()
df_divergencia["valor_monetario"] = df_divergencia["valor"] * 800000
print(df_divergencia["valor"].iloc[-2]) # 1.3547654246160838
print(df_divergencia["valor_monetario"].iloc[-2]) # 1083812.3396928671
# Utilizando decimal
getcontext().prec = 8
df_divergencia = df_selic[(df_selic["data"] >= datetime(2017, 10, 1)) & (df_selic["data"] <= datetime(2022, 11, 1))]
df_divergencia = df_divergencia.sort_values(by=['data'])
df_divergencia["valor"] = df_divergencia["valor"].apply(lambda x: 1 + (Decimal(x.replace(",", ".")) / 100))
df_divergencia["valor"] = df_divergencia["valor"].cumprod()
df_divergencia["valor_monetario"] = df_divergencia["valor"] * 800000
print(df_divergencia["valor"].iloc[-2]) # 1.3553309
print(df_divergencia["valor_monetario"].iloc[-1]) # 1084641.5
Então aqui temos alguns pontos:
Não podemos pegar o fator mês a mês para calcular coisas sem mês fechado, isso complica a vida do usuário e a nossa depois na hora de explicar para o usuário como chegamos neste cálculo.
Utilizando Decimal, temos uma divergência neste dia e ele acrua um valor a mais de alguns bps (base percentage points).. O cálculo utilizando float com truncamento funciona e o valor dá exato.
O cálculo utilizando float com truncamento funciona e o valor dá exato.
Talvez a gente apenas reproduza um erro do BC ao fazer isso, dado que cientificamente o cálculo mais preciso seria usando o decimal, não?
O cálculo do BACEN é o cálculo que os bancos usam, então não seria errado
Mais algumas informações que podem ajudar:
Simulei manualmente no site novo (vejam prints abaixo) e encontrei o seguinte:
fator = decimal.Decimal("1.35476542461604")
. R$ 800.000,00 ajustados dariam R$ 1.083.812,34fator = decimal.Decimal("1.35407771562583")
. R$ 800.000,00 ajustados dariam R$ 1.083.262,17
Fiquei com dúvida se o simulador inclui a taxa do último dia escolhido (ou se termina no dia útil anterior), mas parece que inclui sim, por conta de duas outras simulações que fiz: uma de 1 dia e outra de 0 dias:
Se baixarmos os dados mensais e compararmos com o valor que não inclui o dia 01/11, temos o seguinte:
import datetime
import decimal
dados = [
(datetime.date(2017, 1, 1), Decimal('1.01086120')),
(datetime.date(2017, 2, 1), Decimal('1.00865084')),
(datetime.date(2017, 3, 1), Decimal('1.01052056')),
(datetime.date(2017, 4, 1), Decimal('1.00786581')),
(datetime.date(2017, 5, 1), Decimal('1.00927132')),
(datetime.date(2017, 6, 1), Decimal('1.00808869')),
(datetime.date(2017, 7, 1), Decimal('1.00797923')),
(datetime.date(2017, 8, 1), Decimal('1.00802289')),
(datetime.date(2017, 9, 1), Decimal('1.00638460')),
(datetime.date(2017, 10, 1), Decimal('1.00643930')),
(datetime.date(2017, 11, 1), Decimal('1.00568188')),
(datetime.date(2017, 12, 1), Decimal('1.00538400')),
(datetime.date(2018, 1, 1), Decimal('1.00584205')),
(datetime.date(2018, 2, 1), Decimal('1.00465602')),
(datetime.date(2018, 3, 1), Decimal('1.00532345')),
(datetime.date(2018, 4, 1), Decimal('1.00518295')),
(datetime.date(2018, 5, 1), Decimal('1.00518295')),
(datetime.date(2018, 6, 1), Decimal('1.00518295')),
(datetime.date(2018, 7, 1), Decimal('1.00543042')),
(datetime.date(2018, 8, 1), Decimal('1.00567796')),
(datetime.date(2018, 9, 1), Decimal('1.00468818')),
(datetime.date(2018, 10, 1), Decimal('1.00543042')),
(datetime.date(2018, 11, 1), Decimal('1.00493553')),
(datetime.date(2018, 12, 1), Decimal('1.00493553')),
(datetime.date(2019, 1, 1), Decimal('1.00543042')),
(datetime.date(2019, 2, 1), Decimal('1.00493553')),
(datetime.date(2019, 3, 1), Decimal('1.00468818')),
(datetime.date(2019, 4, 1), Decimal('1.00518295')),
(datetime.date(2019, 5, 1), Decimal('1.00543042')),
(datetime.date(2019, 6, 1), Decimal('1.00468818')),
(datetime.date(2019, 7, 1), Decimal('1.00567796')),
(datetime.date(2019, 8, 1), Decimal('1.00501719')),
(datetime.date(2019, 9, 1), Decimal('1.00463760')),
(datetime.date(2019, 10, 1), Decimal('1.00479264')),
(datetime.date(2019, 11, 1), Decimal('1.00380386')),
(datetime.date(2019, 12, 1), Decimal('1.00374704')),
(datetime.date(2020, 1, 1), Decimal('1.00376633')),
(datetime.date(2020, 2, 1), Decimal('1.00293729')),
(datetime.date(2020, 3, 1), Decimal('1.00338369')),
(datetime.date(2020, 4, 1), Decimal('1.00284925')),
(datetime.date(2020, 5, 1), Decimal('1.00235810')),
(datetime.date(2020, 6, 1), Decimal('1.00212332')),
(datetime.date(2020, 7, 1), Decimal('1.00194346')),
(datetime.date(2020, 8, 1), Decimal('1.00159890')),
(datetime.date(2020, 9, 1), Decimal('1.00156966')),
(datetime.date(2020, 10, 1), Decimal('1.00156966')),
(datetime.date(2020, 11, 1), Decimal('1.00149486')),
(datetime.date(2020, 12, 1), Decimal('1.00164447')),
(datetime.date(2021, 1, 1), Decimal('1.00149486')),
(datetime.date(2021, 2, 1), Decimal('1.00134527')),
(datetime.date(2021, 3, 1), Decimal('1.00201080')),
(datetime.date(2021, 4, 1), Decimal('1.00207785')),
(datetime.date(2021, 5, 1), Decimal('1.00270326')),
(datetime.date(2021, 6, 1), Decimal('1.00307779')),
(datetime.date(2021, 7, 1), Decimal('1.00355616')),
(datetime.date(2021, 8, 1), Decimal('1.00427952')),
(datetime.date(2021, 9, 1), Decimal('1.00441999')),
(datetime.date(2021, 10, 1), Decimal('1.00485996')),
(datetime.date(2021, 11, 1), Decimal('1.00586749')),
(datetime.date(2021, 12, 1), Decimal('1.00769083')),
(datetime.date(2022, 1, 1), Decimal('1.00732270')),
(datetime.date(2022, 2, 1), Decimal('1.00755041')),
(datetime.date(2022, 3, 1), Decimal('1.00927054')),
(datetime.date(2022, 4, 1), Decimal('1.00834321')),
(datetime.date(2022, 5, 1), Decimal('1.01034592')),
(datetime.date(2022, 6, 1), Decimal('1.01015316')),
(datetime.date(2022, 7, 1), Decimal('1.01034842')),
(datetime.date(2022, 8, 1), Decimal('1.01169361')),
(datetime.date(2022, 9, 1), Decimal('1.01071982')),
(datetime.date(2022, 10, 1), Decimal('1.01020676')),
(datetime.date(2022, 11, 1), Decimal('1.01020676')),]
data_inicial = datetime.date(2017, 10, 1)
data_final = datetime.date(2022, 10, 31)
fator_total = 1
for data, fator_mensal in dados:
if data_inicial <= data <= data_final:
fator_total *= fator_mensal
fator_total = fator_total.quantize(Decimal('0.0000000000000001'))
# fator_total: Decimal('1.3547653647828167')
Nota: para chegar nesses dados baixei manualmente, copiei, colei e formatei, ou seja, não usei a biblioteca, pra ter certeza de que o dado é comparável com o que eu peguei nas 2 simulações que fiz por período (usei a mesma interface Web, trocando apenas a opção "período" para "mensal").
O valor ajustado para R$ 800.000,00 por esse fator final que calculei com base no mensal (e arredondado para 16 casas decimais) é de: R$ 1.083.812,29, que é muito próximo da simulação que inclui o dia 01/11/2022 (diferença de 15 centaos).
Ponto importante: o valor relatado pelo @JulioNegreiro (~ R$ 1.087.869,39) é BEM diferente dos valores que obtive acima, então mesmo que ele quisesse somente o ajuste do mês fechado (ou seja, excluindo a possibilidade de 1 dia influenciar) e se a questão das 16 casas decimais fosse resolvida, isso ainda não resolveria o problema. Será que existe diferença entre os valores que estou usando (desse simulador "novoselic" - que é diferente do que foi usado pelo @JulioNegreiro) com os que a biblioteca usa?
Mais um detalhe interessante: a API que o @vmesel citou tem um número bem menor de casas decimais comparado àquele simulador que utilizei: a que possui taxa diária tem 6 dígitos (dividindo por 100 ficariam 8) e a mensal tem apenas 2 dígitos (dividindo por 100 ficariam 4). Sendo assim, não dá pra confiar em outros simuladores (fora do BCB) pois não sabemos de qual lugar no BCB eles pegam a informação (pode ser com mais precisão ou menos precisão).
Pegando a mensal de 11/2022 temos 1.02
. Pegando a diária e multiplicando teríamos 1.01021
(arredondando) -- são 20 dias úteis com taxa igual a 0.050788
, que daria (1 + (0.050788 / 100)) ** 20
.
Além da correção com relação à conta (para ficar igual a do simulador), acho que o ideal seria a biblioteca sempre pegar os valores diários e, caso o usuário passe só o mês, ela utiliza o primeiro dia do mês inicial e o último dia do mês final (calendar.monthrange(year, month)[1]
).
Li em vários manuais que usam a Selic para alguns conta (do BCB, da ANBIMA etc.) que deve-se usar o valor da taxa anual com 2 dígitos, fazendo a conta diária ((1 + taxa_anual) ** (1/252)
) e pegando o resultado dessa conta com 8 dígitos.
Não quero poluir esse Issue, com outro issue.. mas apenas gostaria de levantar um ponto de atenção.
Validando os adaptadores do IPCA também é possível encontrar diferença entre o valor do banco central e o valor do adaptador.
Talvez o problema esteja mais profundo no código do que apenas no tratamento da Selic. ( Por favor conferir as refêrencias utilizadas)
Simulando o IPCA 2017-10 a 2022-10 - 800.000,00 no site- > R$ 1.054.615,28
simulando o IPCA 2017-10 a 2022-10 - 800.000,00 no adaptador -> R$1.050.211,318822023047375160051
from calculadora_do_cidadao import Ipca
ipca = Ipca()
v = ipca.adjust("2017-10", 800000,'2022-10')
print(v)
A diferença é de 4404 reais.
@JulioNegreiro não acho que você está poluindo a issue não, inclusive esse problema é correlato a correção da SELIC. Só para deixar uma projeção registrada que eu fiz usando a data de novembro ao invés de outubro de 2017.
Encontrei uma divergência no uso da biblioteca para ajustar um valor pela selic,
utilizei o código abaixo :
v = 1087869.386433910510912246451
Simulando no site da calculadora do cidadão encontra-se :
data inicial : 01/10/2017 data final : 01/11/2022 valor: 800000
valor corrigido = R$ 1.083.812,34 (REAL)
Os valores apresentam um diferença de R$ 4057,04