-
Notifications
You must be signed in to change notification settings - Fork 93
Expand file tree
/
Copy pathinvestiments.py
More file actions
146 lines (114 loc) · 4.75 KB
/
investiments.py
File metadata and controls
146 lines (114 loc) · 4.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import datetime
from decimal import ROUND_HALF_EVEN, Decimal
from typing import Optional
from django.utils import timezone
class InvestimentBusiness:
"""Lógica de Negócio do Investimento
O investimento renderá 0,52% todos os meses,
no mesmo dia em que for realizado.
Dado que o ganho é pago mensalmente, deve ser tratado como ganho composto,
o que significa que a cada novo período (mês)
o valor ganho passará a fazer parte do saldo do investimento para
o próximo pagamento.
"""
MONTHLY_RATE = Decimal('0.0052')
def __init__(
self,
investiment_value: Decimal,
investiment_created_at: datetime.datetime,
withdrawn_created_at: Optional[datetime.datetime] = None,
):
self.investiment_value = investiment_value
self.investiment_created_at = investiment_created_at
self.withdrawn_created_at = withdrawn_created_at
def calculate_amount(self) -> Decimal:
"""
Calculo do Montante de um investimento aberto(data atual).
"""
initial_date = self.investiment_created_at.date()
today = timezone.localdate()
months = self._full_months_between(initial_date, today)
amount = (
self.investiment_value
* ( # Juros composto
Decimal('1') + self.MONTHLY_RATE
)
** months
)
return amount.quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN)
def calculate_gains(self) -> Decimal:
"""
Cálculo dos ganhos (montante - investido) em um investimento
aberto(Data atual).
"""
amount = self.calculate_amount()
gains = amount - self.investiment_value
return gains.quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN)
def calculate_amount_withdrawn(self) -> Decimal:
"""
Saldo Montante em um investimento fechado.
"""
initial_date = self.investiment_created_at.date()
withdrawn_date = self.withdrawn_created_at.date()
months = self._full_months_between(initial_date, withdrawn_date)
amount = (
self.investiment_value
* (Decimal('1') + self.MONTHLY_RATE) ** months
)
return amount.quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN)
def calculate_gains_withdrawn(self) -> Decimal:
"""Calculo de Ganho em um investimento Fechado."""
amount = self.calculate_amount_withdrawn()
gains = amount - self.investiment_value
return gains.quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN)
def calculate_net_amount_withdrawn(self):
"""
Calculo do montante para saque de um investimento fechado
(A tributar os ganhos).
Se tiver menos de um ano, a percentagem será de **22,5%**
(imposto = 45,00).
Se tiver entre um e dois anos, a percentagem será de **18,5%**
(imposto = 37,00).
Se tiver mais de dois anos, a percentagem será de **15%**
(imposto = 30,00).
"""
amount = self.calculate_amount_withdrawn()
gains = amount - self.investiment_value
initial_date = self.investiment_created_at.date()
withdrawn_date = self.withdrawn_created_at.date()
TAX_UNDER_12 = Decimal('0.225')
TAX_12_TO_24 = Decimal('0.185')
TAX_OVER_24 = Decimal('0.15')
months = self._full_months_between(initial_date, withdrawn_date)
if months < 12:
gains_tax = gains * TAX_UNDER_12
elif months > 24:
gains_tax = gains * TAX_OVER_24
else:
gains_tax = gains * TAX_12_TO_24
return (amount - gains_tax).quantize(
Decimal('0.01'), rounding=ROUND_HALF_EVEN
) # saldo montante(amount) limpo para retorno.
def calculate_net_gains_withdrawn(self) -> Decimal:
"""
Calculo dos Ganhos de um investimento fechado e tributado.
"""
amount = self.calculate_net_amount_withdrawn()
initial_investiment = self.investiment_value
return (amount - initial_investiment).quantize(
Decimal('0.01'), rounding=ROUND_HALF_EVEN
)
def _full_months_between(self, start, end):
"""
Calcula a quantidade de meses **inteiros** entre duas datas.
Um mês só é contabilizado se o período completar um ciclo mensal cheio,
isto é, o dia do mês em `end` deve ser maior ou = dia em `start`.
Regra de negócio:
- Meses parciais não são considerados.
- A contagem só avança quando o mês seguinte é completado.
exemplo: 12/09 -> 11/10 = 0 meses;
"""
months = (end.year - start.year) * 12 + (end.month - start.month)
if end.day < start.day:
months -= 1
return max(months, 0)