1
1
import csv
2
+ import io
3
+ import itertools
2
4
from collections import deque
3
5
from dataclasses import dataclass
4
- from decimal import Decimal
5
6
from datetime import date
6
- import io
7
- import itertools
7
+ from decimal import Decimal
8
8
from typing import List , Optional
9
9
10
10
from dateutil .parser import parse as dateparse
11
11
from dateutil .relativedelta import relativedelta
12
12
13
- from casparser .exceptions import IncompleteCASError , GainsError
14
13
from casparser .enums import FundType , GainType , TransactionType
15
- from casparser .types import CASParserDataType , TransactionDataType
14
+ from casparser .exceptions import GainsError , IncompleteCASError
15
+ from casparser .types import CASData , TransactionData
16
+
16
17
from .utils import CII , get_fin_year , nav_search
17
18
18
19
PURCHASE_TXNS = {
19
- TransactionType .DIVIDEND_REINVEST . name ,
20
- TransactionType .PURCHASE . name ,
21
- TransactionType .PURCHASE_SIP . name ,
22
- TransactionType .REVERSAL . name ,
20
+ TransactionType .DIVIDEND_REINVEST ,
21
+ TransactionType .PURCHASE ,
22
+ TransactionType .PURCHASE_SIP ,
23
+ TransactionType .REVERSAL ,
23
24
# Segregated folios are not supported
24
25
# TransactionType.SEGREGATION.name,
25
- TransactionType .SWITCH_IN . name ,
26
- TransactionType .SWITCH_IN_MERGER . name ,
26
+ TransactionType .SWITCH_IN ,
27
+ TransactionType .SWITCH_IN_MERGER ,
27
28
}
28
29
29
30
SALE_TXNS = {
@@ -87,25 +88,25 @@ class MergedTransaction:
87
88
stt : Decimal = Decimal (0.0 )
88
89
tds : Decimal = Decimal (0.0 )
89
90
90
- def add (self , txn : TransactionDataType ):
91
- txn_type = txn [ " type" ]
92
- if txn_type in PURCHASE_TXNS and txn [ " units" ] is not None :
93
- self .nav = txn [ " nav" ]
94
- self .purchase_units += txn [ " units" ]
95
- self .purchase += txn [ " amount" ]
96
- elif txn_type in SALE_TXNS and txn [ " units" ] is not None :
97
- self .nav = txn [ " nav" ]
98
- self .sale_units += txn [ " units" ]
99
- self .sale += txn [ " amount" ]
100
- elif txn_type == TransactionType .STT_TAX . name :
101
- self .stt += txn [ " amount" ]
102
- elif txn_type == TransactionType .STAMP_DUTY_TAX . name :
103
- self .stamp_duty += txn [ " amount" ]
104
- elif txn_type == TransactionType .TDS_TAX . name :
105
- self .tds += txn [ " amount" ]
106
- elif txn_type == TransactionType .SEGREGATION . name :
91
+ def add (self , txn : TransactionData ):
92
+ txn_type = txn . type
93
+ if txn_type in PURCHASE_TXNS and txn . units is not None :
94
+ self .nav = txn . nav
95
+ self .purchase_units += txn . units
96
+ self .purchase += txn . amount
97
+ elif txn_type in SALE_TXNS and txn . units is not None :
98
+ self .nav = txn . nav
99
+ self .sale_units += txn . units
100
+ self .sale += txn . amount
101
+ elif txn_type == TransactionType .STT_TAX :
102
+ self .stt += txn . amount
103
+ elif txn_type == TransactionType .STAMP_DUTY_TAX :
104
+ self .stamp_duty += txn . amount
105
+ elif txn_type == TransactionType .TDS_TAX :
106
+ self .tds += txn . amount
107
+ elif txn_type == TransactionType .SEGREGATION :
107
108
self .nav = Decimal (0.0 )
108
- self .purchase_units += txn [ " units" ]
109
+ self .purchase_units += txn . units
109
110
self .purchase = Decimal (0.0 )
110
111
111
112
@@ -186,7 +187,7 @@ def index_ratio(self) -> Decimal:
186
187
187
188
@property
188
189
def coa (self ) -> Decimal :
189
- if self .fund .type == FundType .DEBT . name :
190
+ if self .fund .type == FundType .DEBT :
190
191
return Decimal (round (self .purchase_value * self .index_ratio , 2 ))
191
192
if self .purchase_date < self .__cutoff_date :
192
193
if self .sale_date < self .__sell_cutoff_date :
@@ -213,7 +214,7 @@ def stcg(self) -> Decimal:
213
214
return Decimal (0.0 )
214
215
215
216
216
- def get_fund_type (transactions : List [TransactionDataType ]) -> FundType :
217
+ def get_fund_type (transactions : List [TransactionData ]) -> FundType :
217
218
"""
218
219
Detect Fund Type.
219
220
- UNKNOWN if there are no redemption transactions
@@ -225,23 +226,23 @@ def get_fund_type(transactions: List[TransactionDataType]) -> FundType:
225
226
"""
226
227
valid = any (
227
228
[
228
- x [ " units" ] is not None and x [ " units" ] < 0 and x [ " type" ] != TransactionType .REVERSAL . name
229
+ x . units is not None and x . units < 0 and x . type != TransactionType .REVERSAL
229
230
for x in transactions
230
231
]
231
232
)
232
233
if not valid :
233
234
return FundType .UNKNOWN
234
235
return (
235
236
FundType .EQUITY
236
- if any ([x [ " type" ] == TransactionType .STT_TAX . name for x in transactions ])
237
+ if any ([x . type == TransactionType .STT_TAX for x in transactions ])
237
238
else FundType .DEBT
238
239
)
239
240
240
241
241
242
class FIFOUnits :
242
243
"""First-In First-Out units calculator."""
243
244
244
- def __init__ (self , fund : Fund , transactions : List [TransactionDataType ]):
245
+ def __init__ (self , fund : Fund , transactions : List [TransactionData ]):
245
246
"""
246
247
:param fund: name of fund, mainly for reporting purposes.
247
248
:param transactions: list of transactions for the fund
@@ -264,13 +265,13 @@ def __init__(self, fund: Fund, transactions: List[TransactionDataType]):
264
265
@property
265
266
def clean_transactions (self ):
266
267
"""remove redundant transactions, without amount"""
267
- return filter (lambda x : x [ " amount" ] is not None , self ._original_transactions )
268
+ return filter (lambda x : x . amount is not None , self ._original_transactions )
268
269
269
270
def merge_transactions (self ):
270
271
"""Group transactions by date with taxes and investments/redemptions separated."""
271
272
merged_transactions = {}
272
- for txn in sorted (self .clean_transactions , key = lambda x : (x [ " date" ] , - x [ " amount" ] )):
273
- dt = txn [ " date" ]
273
+ for txn in sorted (self .clean_transactions , key = lambda x : (x . date , - x . amount )):
274
+ dt = txn . date
274
275
275
276
if isinstance (dt , str ):
276
277
dt = dateparse (dt ).date ()
@@ -347,8 +348,8 @@ def sell(self, sell_date: date, quantity: Decimal, nav: Decimal, tax: Decimal):
347
348
class CapitalGainsReport :
348
349
"""Generate Capital Gains Report from the parsed CAS data"""
349
350
350
- def __init__ (self , data : CASParserDataType ):
351
- self ._data : CASParserDataType = data
351
+ def __init__ (self , data : CASData ):
352
+ self ._data : CASData = data
352
353
self ._gains : List [GainEntry ] = []
353
354
self .errors = []
354
355
self .invested_amount = Decimal (0.0 )
@@ -370,25 +371,25 @@ def get_fy_list(self) -> List[str]:
370
371
371
372
def process_data (self ):
372
373
self ._gains = []
373
- for folio in self ._data .get ( " folios" , []) :
374
- for scheme in folio .get ( " schemes" , []) :
375
- transactions = scheme [ " transactions" ]
374
+ for folio in self ._data .folios :
375
+ for scheme in folio .schemes :
376
+ transactions = scheme . transactions
376
377
fund = Fund (
377
- scheme = scheme [ " scheme" ] ,
378
- folio = folio [ " folio" ] ,
379
- isin = scheme [ " isin" ] ,
380
- type = scheme [ " type" ] ,
378
+ scheme = scheme . scheme ,
379
+ folio = folio . folio ,
380
+ isin = scheme . isin ,
381
+ type = scheme . type ,
381
382
)
382
383
if len (transactions ) > 0 :
383
- if scheme [ " open" ] >= 0.01 :
384
+ if scheme . open >= 0.01 :
384
385
raise IncompleteCASError (
385
386
"Incomplete CAS found. For gains computation, "
386
387
"all folios should have zero opening balance"
387
388
)
388
389
try :
389
390
fifo = FIFOUnits (fund , transactions )
390
391
self .invested_amount += fifo .invested
391
- self .current_value += scheme [ " valuation" ][ " value" ]
392
+ self .current_value += scheme . valuation . value
392
393
self ._gains .extend (fifo .gains )
393
394
except GainsError as exc :
394
395
self .errors .append ((fund .name , str (exc )))
0 commit comments