11import csv
2+ import io
3+ import itertools
24from collections import deque
35from dataclasses import dataclass
4- from decimal import Decimal
56from datetime import date
6- import io
7- import itertools
7+ from decimal import Decimal
88from typing import List , Optional
99
1010from dateutil .parser import parse as dateparse
1111from dateutil .relativedelta import relativedelta
1212
13- from casparser .exceptions import IncompleteCASError , GainsError
1413from 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+
1617from .utils import CII , get_fin_year , nav_search
1718
1819PURCHASE_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 ,
2324 # Segregated folios are not supported
2425 # TransactionType.SEGREGATION.name,
25- TransactionType .SWITCH_IN . name ,
26- TransactionType .SWITCH_IN_MERGER . name ,
26+ TransactionType .SWITCH_IN ,
27+ TransactionType .SWITCH_IN_MERGER ,
2728}
2829
2930SALE_TXNS = {
@@ -87,25 +88,25 @@ class MergedTransaction:
8788 stt : Decimal = Decimal (0.0 )
8889 tds : Decimal = Decimal (0.0 )
8990
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 :
107108 self .nav = Decimal (0.0 )
108- self .purchase_units += txn [ " units" ]
109+ self .purchase_units += txn . units
109110 self .purchase = Decimal (0.0 )
110111
111112
@@ -186,7 +187,7 @@ def index_ratio(self) -> Decimal:
186187
187188 @property
188189 def coa (self ) -> Decimal :
189- if self .fund .type == FundType .DEBT . name :
190+ if self .fund .type == FundType .DEBT :
190191 return Decimal (round (self .purchase_value * self .index_ratio , 2 ))
191192 if self .purchase_date < self .__cutoff_date :
192193 if self .sale_date < self .__sell_cutoff_date :
@@ -213,7 +214,7 @@ def stcg(self) -> Decimal:
213214 return Decimal (0.0 )
214215
215216
216- def get_fund_type (transactions : List [TransactionDataType ]) -> FundType :
217+ def get_fund_type (transactions : List [TransactionData ]) -> FundType :
217218 """
218219 Detect Fund Type.
219220 - UNKNOWN if there are no redemption transactions
@@ -225,23 +226,23 @@ def get_fund_type(transactions: List[TransactionDataType]) -> FundType:
225226 """
226227 valid = any (
227228 [
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
229230 for x in transactions
230231 ]
231232 )
232233 if not valid :
233234 return FundType .UNKNOWN
234235 return (
235236 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 ])
237238 else FundType .DEBT
238239 )
239240
240241
241242class FIFOUnits :
242243 """First-In First-Out units calculator."""
243244
244- def __init__ (self , fund : Fund , transactions : List [TransactionDataType ]):
245+ def __init__ (self , fund : Fund , transactions : List [TransactionData ]):
245246 """
246247 :param fund: name of fund, mainly for reporting purposes.
247248 :param transactions: list of transactions for the fund
@@ -264,13 +265,13 @@ def __init__(self, fund: Fund, transactions: List[TransactionDataType]):
264265 @property
265266 def clean_transactions (self ):
266267 """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 )
268269
269270 def merge_transactions (self ):
270271 """Group transactions by date with taxes and investments/redemptions separated."""
271272 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
274275
275276 if isinstance (dt , str ):
276277 dt = dateparse (dt ).date ()
@@ -347,8 +348,8 @@ def sell(self, sell_date: date, quantity: Decimal, nav: Decimal, tax: Decimal):
347348class CapitalGainsReport :
348349 """Generate Capital Gains Report from the parsed CAS data"""
349350
350- def __init__ (self , data : CASParserDataType ):
351- self ._data : CASParserDataType = data
351+ def __init__ (self , data : CASData ):
352+ self ._data : CASData = data
352353 self ._gains : List [GainEntry ] = []
353354 self .errors = []
354355 self .invested_amount = Decimal (0.0 )
@@ -370,25 +371,25 @@ def get_fy_list(self) -> List[str]:
370371
371372 def process_data (self ):
372373 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
376377 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 ,
381382 )
382383 if len (transactions ) > 0 :
383- if scheme [ " open" ] >= 0.01 :
384+ if scheme . open >= 0.01 :
384385 raise IncompleteCASError (
385386 "Incomplete CAS found. For gains computation, "
386387 "all folios should have zero opening balance"
387388 )
388389 try :
389390 fifo = FIFOUnits (fund , transactions )
390391 self .invested_amount += fifo .invested
391- self .current_value += scheme [ " valuation" ][ " value" ]
392+ self .current_value += scheme . valuation . value
392393 self ._gains .extend (fifo .gains )
393394 except GainsError as exc :
394395 self .errors .append ((fund .name , str (exc )))
0 commit comments