casparser_isin 2023.9.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- casparser_isin-2023.9.10/CHANGELOG.md +43 -0
- casparser_isin-2023.9.10/LICENSE +21 -0
- casparser_isin-2023.9.10/PKG-INFO +79 -0
- casparser_isin-2023.9.10/README.md +58 -0
- casparser_isin-2023.9.10/casparser_isin/__init__.py +8 -0
- casparser_isin-2023.9.10/casparser_isin/cli.py +131 -0
- casparser_isin-2023.9.10/casparser_isin/isin.db +0 -0
- casparser_isin-2023.9.10/casparser_isin/mf_isin.py +193 -0
- casparser_isin-2023.9.10/casparser_isin/utils.py +15 -0
- casparser_isin-2023.9.10/pyproject.toml +58 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2023.9.10
|
|
4
|
+
- Fallback to old lookup when direct isin search fails
|
|
5
|
+
- update database
|
|
6
|
+
|
|
7
|
+
## 2023.9.3
|
|
8
|
+
- Lookup scheme via isin
|
|
9
|
+
- update database
|
|
10
|
+
|
|
11
|
+
## 2023.8.18
|
|
12
|
+
- fix issues with hdfc mutual fund lookups
|
|
13
|
+
- update database
|
|
14
|
+
|
|
15
|
+
## 2023.1.16
|
|
16
|
+
- DB updates
|
|
17
|
+
|
|
18
|
+
## 2021.7.21 - 2021-07-21
|
|
19
|
+
- better support for Franklin Templeton funds
|
|
20
|
+
- support new CAS pdf files after migration of funds from FTAMIL RTA to CAMS
|
|
21
|
+
|
|
22
|
+
## 2021.7.1 - 2021-07-01
|
|
23
|
+
- add scheme type (`EQUITY`/`DEBT`) to `SchemeData`
|
|
24
|
+
- add nav table for looking up scheme nav for 31-Jan-2018
|
|
25
|
+
|
|
26
|
+
## 2021.6.1 - 2021-06-01
|
|
27
|
+
- support for using custom isin database via `CASPARSER_ISIN_DB` environment variable.
|
|
28
|
+
- updated isin.db
|
|
29
|
+
- packaging fixes
|
|
30
|
+
|
|
31
|
+
## 2021.5.1 - 2021-03-02
|
|
32
|
+
- DB updates
|
|
33
|
+
- Essel mutual funds have been renamed to NAVI
|
|
34
|
+
- Dividend options of funds renamed as IDCW
|
|
35
|
+
|
|
36
|
+
## 2021.4.1 - 2021-04-01
|
|
37
|
+
- updated isin.db
|
|
38
|
+
- updated dependent package versions
|
|
39
|
+
|
|
40
|
+
## 2021.3.1 - 2021-03-02
|
|
41
|
+
- Switch to calendar versioning
|
|
42
|
+
- Fix bugs with version comparison in cli update tool
|
|
43
|
+
- DB files are hosted in CDN for more frequent updates via CLI. [pypi releases will be limited to major changes in codebase]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Sandeep Somasekharan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: casparser_isin
|
|
3
|
+
Version: 2023.9.10
|
|
4
|
+
Summary: ISIN database for casparser
|
|
5
|
+
Home-page: https://github.com/codereverser/casparser-isin
|
|
6
|
+
License: MIT
|
|
7
|
+
Author: Sandeep Somasekharan
|
|
8
|
+
Author-email: codereverser@gmail.com
|
|
9
|
+
Requires-Python: >=3.8,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Requires-Dist: packaging (>=20.9)
|
|
18
|
+
Requires-Dist: rapidfuzz (>=3.2.0,<4.0.0)
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# CASParser-ISIN
|
|
22
|
+
|
|
23
|
+
[](https://github.com/psf/black)
|
|
24
|
+
[](https://github.com/codereverser/casparser/blob/main/LICENSE)
|
|
25
|
+

|
|
26
|
+
[](https://codecov.io/gh/codereverser/casparser-isin)
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
ISIN Database for [casparser](https://github.com/codereverser/casparser).
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
```bash
|
|
33
|
+
pip install -U casparser-isin
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from casparser_isin import MFISINDb
|
|
41
|
+
with MFISINDb() as db:
|
|
42
|
+
scheme_data = db.isin_lookup("Axis Long Term Equity Fund - Growth", # scheme name
|
|
43
|
+
"KFINTECH", # RTA
|
|
44
|
+
"128TSDGG", # Scheme RTA code
|
|
45
|
+
)
|
|
46
|
+
print(scheme_data)
|
|
47
|
+
```
|
|
48
|
+
```
|
|
49
|
+
SchemeData(name="axis long term equity fund - direct growth",
|
|
50
|
+
isin="INF846K01EW2",
|
|
51
|
+
amfi_code="120503",
|
|
52
|
+
score=100.0)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The database also contains NAV values on 31-Jan-2018 for all funds, which can be used for
|
|
56
|
+
taxable LTCG computation for units purchased before the same date.
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
from casparser_isin import MFISINDb
|
|
60
|
+
with MFISINDb() as db:
|
|
61
|
+
nav = db.nav_lookup("INF846K01EW2")
|
|
62
|
+
print(nav)
|
|
63
|
+
```
|
|
64
|
+
```
|
|
65
|
+
Decimal('44.8938')
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
## Notes
|
|
70
|
+
|
|
71
|
+
- casparser-isin is shipped with a local database which may get obsolete over time. The local
|
|
72
|
+
database can be updated via the cli tool
|
|
73
|
+
|
|
74
|
+
```shell
|
|
75
|
+
casparser-isin --update
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- casparser-isin will try to use the file provided by `CASPARSER_ISIN_DB` environment variable; if present, and the file exists
|
|
79
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# CASParser-ISIN
|
|
2
|
+
|
|
3
|
+
[](https://github.com/psf/black)
|
|
4
|
+
[](https://github.com/codereverser/casparser/blob/main/LICENSE)
|
|
5
|
+

|
|
6
|
+
[](https://codecov.io/gh/codereverser/casparser-isin)
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
ISIN Database for [casparser](https://github.com/codereverser/casparser).
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
```bash
|
|
13
|
+
pip install -U casparser-isin
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from casparser_isin import MFISINDb
|
|
21
|
+
with MFISINDb() as db:
|
|
22
|
+
scheme_data = db.isin_lookup("Axis Long Term Equity Fund - Growth", # scheme name
|
|
23
|
+
"KFINTECH", # RTA
|
|
24
|
+
"128TSDGG", # Scheme RTA code
|
|
25
|
+
)
|
|
26
|
+
print(scheme_data)
|
|
27
|
+
```
|
|
28
|
+
```
|
|
29
|
+
SchemeData(name="axis long term equity fund - direct growth",
|
|
30
|
+
isin="INF846K01EW2",
|
|
31
|
+
amfi_code="120503",
|
|
32
|
+
score=100.0)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The database also contains NAV values on 31-Jan-2018 for all funds, which can be used for
|
|
36
|
+
taxable LTCG computation for units purchased before the same date.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
from casparser_isin import MFISINDb
|
|
40
|
+
with MFISINDb() as db:
|
|
41
|
+
nav = db.nav_lookup("INF846K01EW2")
|
|
42
|
+
print(nav)
|
|
43
|
+
```
|
|
44
|
+
```
|
|
45
|
+
Decimal('44.8938')
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Notes
|
|
50
|
+
|
|
51
|
+
- casparser-isin is shipped with a local database which may get obsolete over time. The local
|
|
52
|
+
database can be updated via the cli tool
|
|
53
|
+
|
|
54
|
+
```shell
|
|
55
|
+
casparser-isin --update
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- casparser-isin will try to use the file provided by `CASPARSER_ISIN_DB` environment variable; if present, and the file exists
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
from packaging import version
|
|
4
|
+
import sqlite3
|
|
5
|
+
import sys
|
|
6
|
+
from urllib.error import HTTPError
|
|
7
|
+
from urllib import request
|
|
8
|
+
|
|
9
|
+
from . import __version__
|
|
10
|
+
from .utils import get_isin_db_path
|
|
11
|
+
|
|
12
|
+
META_URL = "https://casparser.atomcoder.com/isin.db.meta"
|
|
13
|
+
DB_URL = "https://casparser.atomcoder.com/isin.db"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_metadata():
|
|
17
|
+
conn = sqlite3.connect(get_isin_db_path())
|
|
18
|
+
cursor = conn.cursor()
|
|
19
|
+
try:
|
|
20
|
+
with conn:
|
|
21
|
+
cursor.execute("SELECT key, value from meta")
|
|
22
|
+
metadata = {}
|
|
23
|
+
for key, value in cursor.fetchall():
|
|
24
|
+
if key in ("dbformat", "version"):
|
|
25
|
+
value = version.parse(value)
|
|
26
|
+
metadata[key] = value
|
|
27
|
+
metadata["cli-version"] = version.parse(__version__)
|
|
28
|
+
return metadata
|
|
29
|
+
finally:
|
|
30
|
+
cursor.close()
|
|
31
|
+
conn.close()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def print_version():
|
|
35
|
+
metadata = get_metadata()
|
|
36
|
+
print(f"cli-version : {metadata['cli-version']}")
|
|
37
|
+
print(f"db-version : {metadata['version']}")
|
|
38
|
+
print(f"db-format : {metadata['dbformat']}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_request(url):
|
|
42
|
+
hdr = {
|
|
43
|
+
"User Agent": f"casparser-isin {__version__}",
|
|
44
|
+
"X-origin-casparser": "true",
|
|
45
|
+
}
|
|
46
|
+
return request.Request(url, headers=hdr)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_isin_db_details():
|
|
50
|
+
local_meta = get_metadata()
|
|
51
|
+
remote_meta = None
|
|
52
|
+
logging.info("Fetching remote isin db metadata")
|
|
53
|
+
try:
|
|
54
|
+
with request.urlopen(build_request(META_URL)) as response:
|
|
55
|
+
data = response.read().decode()
|
|
56
|
+
except HTTPError as e:
|
|
57
|
+
logging.error("Received error from remote server :: %s", e.reason)
|
|
58
|
+
else:
|
|
59
|
+
remote_meta = {}
|
|
60
|
+
for line in data.splitlines():
|
|
61
|
+
split = line.split("=")
|
|
62
|
+
if len(split) == 2:
|
|
63
|
+
key, value = [x.strip() for x in split]
|
|
64
|
+
if key in ("dbformat", "version"):
|
|
65
|
+
value = version.parse(value)
|
|
66
|
+
remote_meta[key] = value
|
|
67
|
+
logging.info("Local db version : %s", local_meta.get("version"))
|
|
68
|
+
logging.info("Remote db version : %s", remote_meta.get("version"))
|
|
69
|
+
return remote_meta, local_meta
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def check_isin_db():
|
|
73
|
+
"""Compare remote and local db versions
|
|
74
|
+
Return code:
|
|
75
|
+
0 - no new database available
|
|
76
|
+
1 - new database available
|
|
77
|
+
"""
|
|
78
|
+
remote_meta, local_meta = get_isin_db_details()
|
|
79
|
+
if (
|
|
80
|
+
remote_meta is not None
|
|
81
|
+
and remote_meta["version"] > local_meta["version"]
|
|
82
|
+
and remote_meta["dbformat"] == local_meta["dbformat"]
|
|
83
|
+
):
|
|
84
|
+
logging.info("To update the database, re-run the command with --update flag.")
|
|
85
|
+
sys.exit(1)
|
|
86
|
+
else:
|
|
87
|
+
logging.info("Local database is up to date.")
|
|
88
|
+
sys.exit(0)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def update_isin_db():
|
|
92
|
+
remote_meta, local_meta = get_isin_db_details()
|
|
93
|
+
if remote_meta is None:
|
|
94
|
+
return
|
|
95
|
+
elif (
|
|
96
|
+
remote_meta["version"] > local_meta["version"]
|
|
97
|
+
and remote_meta["dbformat"] == local_meta["dbformat"]
|
|
98
|
+
):
|
|
99
|
+
logging.info("Fetching database version :: %s", remote_meta["version"])
|
|
100
|
+
try:
|
|
101
|
+
with request.urlopen(build_request(DB_URL)) as response:
|
|
102
|
+
data = response.read()
|
|
103
|
+
except HTTPError as e:
|
|
104
|
+
logging.error("Error fetching isin database :: %s", e.reason)
|
|
105
|
+
return
|
|
106
|
+
with open(get_isin_db_path(), "wb") as f:
|
|
107
|
+
f.write(data)
|
|
108
|
+
logging.info("Updated casparser-isin database.")
|
|
109
|
+
else:
|
|
110
|
+
logging.info("casparser-isin database is already upto date")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def main():
|
|
114
|
+
parser = argparse.ArgumentParser("casparser-isin", description="casparser-isin cli")
|
|
115
|
+
parser.add_argument("-v", "--version", help="Print version information", action="store_true")
|
|
116
|
+
parser.add_argument("--update", help="Update isin database", action="store_true")
|
|
117
|
+
parser.add_argument("--check", help="Check remote isin database version", action="store_true")
|
|
118
|
+
args = parser.parse_args()
|
|
119
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
|
120
|
+
if args.version:
|
|
121
|
+
print_version()
|
|
122
|
+
elif args.update:
|
|
123
|
+
update_isin_db()
|
|
124
|
+
elif args.check:
|
|
125
|
+
check_isin_db()
|
|
126
|
+
else:
|
|
127
|
+
parser.print_help()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if __name__ == "__main__":
|
|
131
|
+
main()
|
|
Binary file
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from collections import namedtuple
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
import re
|
|
4
|
+
import sqlite3
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from rapidfuzz import process, utils
|
|
8
|
+
|
|
9
|
+
from .utils import get_isin_db_path
|
|
10
|
+
|
|
11
|
+
RTA_MAP = {
|
|
12
|
+
"CAMS": "CAMS",
|
|
13
|
+
"FTAMIL": "FRANKLIN",
|
|
14
|
+
"FRANKLIN": "FRANKLIN",
|
|
15
|
+
"KFINTECH": "KARVY",
|
|
16
|
+
"KARVY": "KARVY",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
SchemeData = namedtuple("SchemeData", "name isin amfi_code type score")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def dict_factory(cursor, row):
|
|
23
|
+
d = {}
|
|
24
|
+
for idx, col in enumerate(cursor.description):
|
|
25
|
+
d[col[0]] = row[idx]
|
|
26
|
+
return d
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MFISINDb:
|
|
30
|
+
"""ISIN database for (Indian) Mutual Funds."""
|
|
31
|
+
|
|
32
|
+
connection = None
|
|
33
|
+
cursor = None
|
|
34
|
+
|
|
35
|
+
def __enter__(self):
|
|
36
|
+
self.initialize()
|
|
37
|
+
return self
|
|
38
|
+
|
|
39
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
40
|
+
self.close()
|
|
41
|
+
|
|
42
|
+
def initialize(self):
|
|
43
|
+
"""Initialize database."""
|
|
44
|
+
self.connection = sqlite3.connect(get_isin_db_path())
|
|
45
|
+
self.connection.row_factory = dict_factory
|
|
46
|
+
self.cursor = self.connection.cursor()
|
|
47
|
+
|
|
48
|
+
def close(self):
|
|
49
|
+
"""Close database connection."""
|
|
50
|
+
if self.cursor is not None:
|
|
51
|
+
self.cursor.close()
|
|
52
|
+
self.cursor = None
|
|
53
|
+
if self.connection is not None:
|
|
54
|
+
self.connection.close()
|
|
55
|
+
self.connection = None
|
|
56
|
+
|
|
57
|
+
def run_query(self, sql, arguments, fetchone=False):
|
|
58
|
+
self_initialized = False
|
|
59
|
+
if self.connection is None:
|
|
60
|
+
self.initialize()
|
|
61
|
+
self_initialized = True
|
|
62
|
+
try:
|
|
63
|
+
self.cursor.execute(sql, arguments)
|
|
64
|
+
if fetchone:
|
|
65
|
+
return self.cursor.fetchone()
|
|
66
|
+
return self.cursor.fetchall()
|
|
67
|
+
finally:
|
|
68
|
+
if self_initialized:
|
|
69
|
+
self.close()
|
|
70
|
+
|
|
71
|
+
def direct_isin_lookup(self, isin: str):
|
|
72
|
+
"""
|
|
73
|
+
Lookup scheme data via ISIN code
|
|
74
|
+
:param isin: Fund ISIN
|
|
75
|
+
:return:
|
|
76
|
+
"""
|
|
77
|
+
sql = """SELECT name, isin, amfi_code, type from scheme WHERE isin = :isin"""
|
|
78
|
+
return self.run_query(sql, {"isin": isin})
|
|
79
|
+
|
|
80
|
+
def scheme_lookup(self, rta: str, scheme_name: str, rta_code: str):
|
|
81
|
+
"""
|
|
82
|
+
Lookup scheme details from the database
|
|
83
|
+
:param rta: RTA (CAMS, KARVY, FTAMIL)
|
|
84
|
+
:param scheme_name: scheme name
|
|
85
|
+
:param rta_code: RTA code for the scheme
|
|
86
|
+
:return:
|
|
87
|
+
"""
|
|
88
|
+
if rta_code is not None:
|
|
89
|
+
rta_code = re.sub(r"\s+", "", rta_code)
|
|
90
|
+
|
|
91
|
+
sql = """SELECT name, isin, amfi_code, type from scheme"""
|
|
92
|
+
where = ["rta = :rta"]
|
|
93
|
+
|
|
94
|
+
if re.search(r"fti(\d+)", rta_code, re.I) and rta.upper() in ("CAMS", "FRANKLIN", "FTAMIL"):
|
|
95
|
+
# Try searching db for Franklin schemes
|
|
96
|
+
where_ = ["rta = :rta", "rta_code = :rta_code"]
|
|
97
|
+
args = {"rta": "FRANKLIN", "rta_code": rta_code}
|
|
98
|
+
sql_statement = "{} WHERE {}".format(sql, " AND ".join(where_))
|
|
99
|
+
results = self.run_query(sql_statement, args)
|
|
100
|
+
if len(results) != 0:
|
|
101
|
+
return results
|
|
102
|
+
|
|
103
|
+
args = {"rta": RTA_MAP.get(str(rta).upper(), ""), "rta_code": rta_code}
|
|
104
|
+
|
|
105
|
+
if "hdfc" in scheme_name.lower():
|
|
106
|
+
if re.search("direct", scheme_name, re.I):
|
|
107
|
+
where.append("name LIKE '%direct%'")
|
|
108
|
+
else:
|
|
109
|
+
where.append("name NOT LIKE '%direct%'")
|
|
110
|
+
|
|
111
|
+
if re.search("dividend|idcw", scheme_name, re.I):
|
|
112
|
+
if re.search("re-*invest", scheme_name, re.I):
|
|
113
|
+
where.append("name LIKE '%reinvest%'")
|
|
114
|
+
else:
|
|
115
|
+
where.append("name LIKE '%payout%'")
|
|
116
|
+
where.append("rta_code like :rta_code_d")
|
|
117
|
+
args.update(rta_code_d=f"{rta_code}%")
|
|
118
|
+
else:
|
|
119
|
+
where.append("rta_code = :rta_code")
|
|
120
|
+
|
|
121
|
+
sql_statement = "{} WHERE {} GROUP BY isin".format(sql, " AND ".join(where))
|
|
122
|
+
results = self.run_query(sql_statement, args)
|
|
123
|
+
if len(results) == 0 and "rta_code" in args:
|
|
124
|
+
args["rta_code"] = args["rta_code"][:-1]
|
|
125
|
+
results = self.run_query(sql_statement, args)
|
|
126
|
+
return results
|
|
127
|
+
|
|
128
|
+
def isin_lookup(
|
|
129
|
+
self,
|
|
130
|
+
scheme_name: str,
|
|
131
|
+
rta: str,
|
|
132
|
+
rta_code: str,
|
|
133
|
+
isin: Optional[str] = None,
|
|
134
|
+
min_score: int = 60,
|
|
135
|
+
) -> SchemeData:
|
|
136
|
+
"""
|
|
137
|
+
Return the closest matching scheme from MF isin database.
|
|
138
|
+
|
|
139
|
+
:param scheme_name: Scheme Name
|
|
140
|
+
:param rta: RTA (CAMS, KARVY, KFINTECH)
|
|
141
|
+
:param rta_code: Scheme RTA code
|
|
142
|
+
:param isin: Fund ISIN
|
|
143
|
+
:param min_score: Minimum score (out of 100) required from the fuzzy match algorithm
|
|
144
|
+
|
|
145
|
+
:return: isin and amfi_code code for matching scheme.
|
|
146
|
+
:rtype: SchemeData
|
|
147
|
+
:raises: ValueError if no scheme is found in the database.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
if not (
|
|
151
|
+
isinstance(scheme_name, str) and isinstance(rta, str) and isinstance(rta_code, str)
|
|
152
|
+
):
|
|
153
|
+
raise TypeError("Invalid input")
|
|
154
|
+
if rta.upper() not in RTA_MAP:
|
|
155
|
+
raise ValueError(f"Invalid RTA : {rta}")
|
|
156
|
+
results = []
|
|
157
|
+
if isin is not None:
|
|
158
|
+
results = self.direct_isin_lookup(isin)
|
|
159
|
+
if len(results) == 0:
|
|
160
|
+
results = self.scheme_lookup(rta, scheme_name, rta_code)
|
|
161
|
+
if len(results) == 1:
|
|
162
|
+
result = results[0]
|
|
163
|
+
return SchemeData(
|
|
164
|
+
name=result["name"],
|
|
165
|
+
isin=result["isin"],
|
|
166
|
+
amfi_code=result["amfi_code"],
|
|
167
|
+
type=result["type"],
|
|
168
|
+
score=100,
|
|
169
|
+
)
|
|
170
|
+
elif len(results) > 1:
|
|
171
|
+
schemes = {
|
|
172
|
+
x["name"]: (x["name"], x["isin"], x["amfi_code"], x["type"]) for x in results
|
|
173
|
+
}
|
|
174
|
+
key, score, _ = process.extractOne(
|
|
175
|
+
scheme_name, schemes.keys(), processor=utils.default_process
|
|
176
|
+
)
|
|
177
|
+
if score >= min_score:
|
|
178
|
+
name, isin, amfi_code, scheme_type = schemes[key]
|
|
179
|
+
return SchemeData(
|
|
180
|
+
name=name, isin=isin, amfi_code=amfi_code, type=scheme_type, score=score
|
|
181
|
+
)
|
|
182
|
+
raise ValueError("No schemes found")
|
|
183
|
+
|
|
184
|
+
def nav_lookup(self, isin: str) -> Optional[Decimal]:
|
|
185
|
+
"""
|
|
186
|
+
Return the NAV of the fund on 31st Jan 2018. used for LTCG computations
|
|
187
|
+
:param isin: Fund ISIN
|
|
188
|
+
:return: nav value as a Decimal if available, else return None
|
|
189
|
+
"""
|
|
190
|
+
sql = """SELECT nav FROM nav20180131 where isin = :isin"""
|
|
191
|
+
result = self.run_query(sql, {"isin": isin}, fetchone=True)
|
|
192
|
+
if result is not None:
|
|
193
|
+
return Decimal(result["nav"])
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pathlib
|
|
3
|
+
|
|
4
|
+
BASE_DIR = pathlib.Path(__file__).resolve().parent
|
|
5
|
+
INTERNAL_ISIN_DB_PATH = BASE_DIR / "isin.db"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_isin_db_path():
|
|
9
|
+
env_isin_path = os.getenv("CASPARSER_ISIN_DB")
|
|
10
|
+
try:
|
|
11
|
+
if os.path.exists(env_isin_path) and os.path.isfile(env_isin_path):
|
|
12
|
+
return pathlib.Path(env_isin_path)
|
|
13
|
+
except TypeError:
|
|
14
|
+
pass
|
|
15
|
+
return INTERNAL_ISIN_DB_PATH
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "casparser_isin"
|
|
3
|
+
version = "0"
|
|
4
|
+
description = "ISIN database for casparser"
|
|
5
|
+
authors = ["Sandeep Somasekharan <codereverser@gmail.com>"]
|
|
6
|
+
homepage = "https://github.com/codereverser/casparser-isin"
|
|
7
|
+
license = "MIT License"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
classifiers = [
|
|
10
|
+
"License :: OSI Approved :: MIT License",
|
|
11
|
+
"Programming Language :: Python :: 3.8",
|
|
12
|
+
"Programming Language :: Python :: 3.9",
|
|
13
|
+
"Programming Language :: Python :: 3.10",
|
|
14
|
+
"Operating System :: OS Independent"
|
|
15
|
+
]
|
|
16
|
+
include = [ "CHANGELOG.md" ]
|
|
17
|
+
|
|
18
|
+
[tool.poetry.dependencies]
|
|
19
|
+
python = "^3.8"
|
|
20
|
+
packaging = ">=20.9"
|
|
21
|
+
rapidfuzz = "^3.2.0"
|
|
22
|
+
|
|
23
|
+
[tool.poetry.dev-dependencies]
|
|
24
|
+
coverage = {version = "^7.3.0", extras=["toml"]}
|
|
25
|
+
pytest = "^7.4.0"
|
|
26
|
+
pytest-cov = "^4.1.0"
|
|
27
|
+
apsw = "^3.43.0"
|
|
28
|
+
b2sdk = "^1.24.0"
|
|
29
|
+
lxml = "^4.9.0"
|
|
30
|
+
python-dotenv = "^1.0.0"
|
|
31
|
+
requests = "^2.31.0"
|
|
32
|
+
requests-cache = "^1.1.0"
|
|
33
|
+
pre-commit = "^3.4.0"
|
|
34
|
+
|
|
35
|
+
[tool.poetry.scripts]
|
|
36
|
+
casparser-isin = "casparser_isin.cli:main"
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["poetry-core >= 1.0.0"]
|
|
40
|
+
build-backend = "poetry.core.masonry.api"
|
|
41
|
+
|
|
42
|
+
[tool.black]
|
|
43
|
+
line-length = 100
|
|
44
|
+
target-version = ["py38"]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
minversion = "6.0"
|
|
48
|
+
addopts = "--cov=casparser_isin --cov-config=tox.ini --cov-report=xml --cov-report=html --exitfirst"
|
|
49
|
+
testpaths = [
|
|
50
|
+
"tests",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[tool.ruff]
|
|
54
|
+
line-length = 100
|
|
55
|
+
target-version = "py38"
|
|
56
|
+
|
|
57
|
+
[tool.poetry-version-plugin]
|
|
58
|
+
source = "init"
|