ns-devel commited on
Commit
38171fa
1 Parent(s): b9267ce

Text2SQL app

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +61 -0
  2. Dockerfile +11 -0
  3. core/__init__.py +0 -0
  4. core/admin.py +16 -0
  5. core/apps.py +6 -0
  6. core/clients/__init__.py +0 -0
  7. core/clients/mutual_fund.py +267 -0
  8. core/clients/stock.py +206 -0
  9. core/constants.py +4 -0
  10. core/cron.py +9 -0
  11. core/management/commands/text2sql_eval.py +50 -0
  12. core/management/commands/update_mf.py +92 -0
  13. core/mfrating/score_calculator.py +235 -0
  14. core/middleware.py +55 -0
  15. core/migrations/0001_initial.py +41 -0
  16. core/migrations/0002_rename_fund_house_mutualfund_fund_name.py +18 -0
  17. core/migrations/0002_stock.py +41 -0
  18. core/migrations/0003_alter_mutualfund_fund_name.py +18 -0
  19. core/migrations/0003_alter_stock_link.py +18 -0
  20. core/migrations/0004_stock_isin_number.py +18 -0
  21. core/migrations/0005_merge_20231211_0610.py +13 -0
  22. core/migrations/0006_mutualfund_crisil_rank.py +18 -0
  23. core/migrations/0007_merge_20231214_1924.py +13 -0
  24. core/migrations/0008_mutualfund_aum.py +18 -0
  25. core/migrations/0009_mutualfund_expense_ratio_mutualfund_return_m12_and_more.py +106 -0
  26. core/migrations/0010_remove_mfholdings_nav_mutualfund_nav.py +22 -0
  27. core/migrations/0011_remove_mfholdings_maturity_date_and_more.py +22 -0
  28. core/migrations/0012_alter_mfholdings_isin_number.py +18 -0
  29. core/migrations/0013_alter_mfholdings_country_alter_mfholdings_currency_and_more.py +63 -0
  30. core/migrations/0014_alter_mfholdings_currency_and_more.py +48 -0
  31. core/migrations/0015_rename_security_name_mfholdings_holding_name_and_more.py +22 -0
  32. core/migrations/__init__.py +0 -0
  33. core/models.py +100 -0
  34. core/test_models.py +42 -0
  35. core/tests/__init__.py +0 -0
  36. core/tests/data.py +339 -0
  37. core/tests/test_scores.py +101 -0
  38. core/text2sql/__init__.py +0 -0
  39. core/text2sql/eval_queries.py +86 -0
  40. core/text2sql/handler.py +26 -0
  41. core/text2sql/ml_models.py +24 -0
  42. core/text2sql/prompt.py +69 -0
  43. core/urls.py +7 -0
  44. core/views.py +30 -0
  45. data_pipeline/__init__.py +0 -0
  46. data_pipeline/admin.py +3 -0
  47. data_pipeline/apps.py +6 -0
  48. data_pipeline/interfaces/__init__.py +0 -0
  49. data_pipeline/interfaces/api_client.py +36 -0
  50. data_pipeline/interfaces/test_api_client.py +35 -0
.gitignore ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # These are some examples of commonly ignored file patterns.
2
+ # You should customize this list as applicable to your project.
3
+ # Learn more about .gitignore:
4
+ # https://www.atlassian.com/git/tutorials/saving-changes/gitignore
5
+
6
+ # Node artifact files
7
+ node_modules/
8
+ dist/
9
+
10
+ # Compiled Java class files
11
+ *.class
12
+
13
+ # Compiled Python bytecode
14
+ *.py[cod]
15
+
16
+ # Log files
17
+ *.log
18
+
19
+ # Package files
20
+ *.jar
21
+
22
+ # Maven
23
+ target/
24
+ dist/
25
+
26
+ # JetBrains IDE
27
+ .idea/
28
+
29
+ # Unit test reports
30
+ TEST*.xml
31
+
32
+ # Generated by MacOS
33
+ .DS_Store
34
+
35
+ # Generated by Windows
36
+ Thumbs.db
37
+
38
+ # Applications
39
+ *.app
40
+ *.exe
41
+ *.war
42
+
43
+ # Large media files
44
+ *.mp4
45
+ *.tiff
46
+ *.avi
47
+ *.flv
48
+ *.mov
49
+ *.wmv
50
+ # These are some examples of commonly ignored file patterns.
51
+ # You should customize this list as applicable to your project.
52
+ # Learn more about .gitignore:
53
+ # https://www.atlassian.com/git/tutorials/saving-changes/gitignore
54
+
55
+ venv/
56
+ .venv/
57
+
58
+ **/__pycache__/
59
+ data/*
60
+ env.sh
61
+ db.sqlite3
Dockerfile ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11
2
+
3
+ WORKDIR /code
4
+
5
+ COPY ./requirements.txt /code/requirements.txt
6
+
7
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
8
+
9
+ COPY . .
10
+
11
+ CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
core/__init__.py ADDED
File without changes
core/admin.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This file is used to register the models in the admin panel.
3
+ """
4
+
5
+ from django.contrib import admin
6
+ from core.models import MutualFund, Stock
7
+
8
+
9
+ @admin.register(MutualFund)
10
+ class MutualFundAdmin(admin.ModelAdmin):
11
+ list_display = ("id", "rank", "fund_name", "isin_number", "security_id")
12
+
13
+
14
+ @admin.register(Stock)
15
+ class StockAdmin(admin.ModelAdmin):
16
+ pass
core/apps.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class CoreConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "core"
core/clients/__init__.py ADDED
File without changes
core/clients/mutual_fund.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+
3
+ """
4
+ import logging
5
+
6
+ from django.conf import settings
7
+
8
+ import requests
9
+ from bs4 import BeautifulSoup
10
+ from core.models import MutualFund
11
+ from core.constants import MONEYCONTROL_TOPFUNDS_URL
12
+ from data_pipeline.interfaces.api_client import DataClient
13
+
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ settings.MORNINGSTAR_API_HEADERS = {
19
+ "X-RapidAPI-Key": settings.MORNINGSTAR_KEY,
20
+ "X-RapidAPI-Host": settings.MORNINGSTAR_HOST,
21
+ }
22
+
23
+
24
+ class MFList(DataClient):
25
+ model = MutualFund
26
+
27
+ # Monring Star List MF url
28
+ api_url = "https://lt.morningstar.com/api/rest.svc/g9vi2nsqjb/security/screener?page=1&pageSize=15000&sortOrder=name%20asc&outputType=json&version=1&languageId=en&currencyId=INR&universeIds=FOIND%24%24ALL%7CFCIND%24%24ALL&securityDataPoints=secId%2ClegalName%2CclosePrice%2CclosePriceDate%2Cyield_M12%2CongoingCharge%2CcategoryName%2CMedalist_RatingNumber%2CstarRatingM255%2CreturnD1%2CreturnW1%2CreturnM1%2CreturnM3%2CreturnM6%2CreturnM0%2CreturnM12%2CreturnM36%2CreturnM60%2CreturnM120%2CmaxFrontEndLoad%2CmanagerTenure%2CmaxDeferredLoad%2CexpenseRatio%2Cisin%2CinitialPurchase%2CfundTnav%2CequityStyleBox%2CbondStyleBox%2CaverageMarketCapital%2CaverageCreditQualityCode%2CeffectiveDuration%2CmorningstarRiskM255%2CalphaM36%2CbetaM36%2Cr2M36%2CstandardDeviationM36%2CsharpeM36%2CtrackRecordExtension&filters=&term="
29
+
30
+ def __init__(self) -> None:
31
+ self.api_response = None
32
+ self.transformed_data = None
33
+
34
+ def extract(self):
35
+
36
+ super().extract()
37
+
38
+ logger.info("Calling Morningstar API")
39
+ response = requests.get(self.api_url)
40
+
41
+ # Check if the request was successful (status code 200)
42
+ if response.status_code == 200:
43
+
44
+ # Parse JSON response
45
+ self.api_response = response.json()
46
+ logger.info(
47
+ f'Morningstar API response received {len(self.api_response["rows"])} funds'
48
+ )
49
+
50
+ else:
51
+ logger.info("Received status code: {response.status_code}")
52
+ logger.info(response.json())
53
+
54
+ def transform(self):
55
+ """
56
+ Transform the data to the format required by the model
57
+ """
58
+
59
+ super().transform()
60
+
61
+ self.transformed_data = [
62
+ {
63
+ "fund_name": fund["legalName"],
64
+ "isin_number": fund.get("isin"),
65
+ "security_id": fund["secId"],
66
+ "data": {"list_info": fund},
67
+ }
68
+ for fund in self.api_response["rows"]
69
+ ]
70
+
71
+ def load(self):
72
+ """
73
+ Load the data into the database
74
+ """
75
+
76
+ create_count = 0
77
+ update_count = 0
78
+ for data_dict in self.transformed_data:
79
+ try:
80
+ mf = self.model.objects.get(isin_number=data_dict["isin_number"])
81
+ mf.data.update(data_dict["data"])
82
+ mf.save()
83
+ update_count += 1
84
+ except self.model.DoesNotExist:
85
+ mf = self.model(**data_dict)
86
+ mf.save()
87
+ create_count += 1
88
+
89
+ logger.info(
90
+ "Created %s records; Updated %s records", create_count, update_count
91
+ )
92
+
93
+
94
+ class MFQuote(DataClient):
95
+ model = MutualFund
96
+
97
+ # Monring Star get quote url
98
+ api_url = f"https://{settings.MORNINGSTAR_HOST}/etf/get-quote"
99
+
100
+ def __init__(self, isin) -> None:
101
+ self.api_response = None # {"quotes": None, "holdings": None}
102
+ self.transformed_data = None
103
+ self.isin = isin
104
+ self.mf = self.model.objects.get(isin_number=self.isin)
105
+
106
+ def extract(self):
107
+
108
+ logger.info(f"Calling Morningstar Quote API for quotes with isin {self.isin}")
109
+ querystring = {"securityId": self.mf.security_id}
110
+
111
+ response = requests.get(
112
+ self.api_url, headers=settings.MORNINGSTAR_API_HEADERS, params=querystring
113
+ )
114
+
115
+ # Check if the request was successful (status code 200)
116
+ if response.status_code == 200:
117
+ # Parse JSON response
118
+ self.api_response = response.json()
119
+ else:
120
+ logger.info(f"API response: %s", response.status_code)
121
+ response.raise_for_status()
122
+
123
+ def load(self):
124
+ self.mf.data.update({"quotes": self.transformed_data})
125
+ self.mf.save()
126
+ logger.info(f"Successfully stored data of quotes for {self.mf.fund_name}")
127
+
128
+
129
+ class MFHoldings(DataClient):
130
+ model = MutualFund
131
+ api_url = f"https://{settings.MORNINGSTAR_HOST}/etf/portfolio/get-holdings"
132
+
133
+ def __init__(self, isin) -> None:
134
+ self.api_response = None
135
+ self.transformed_data = None
136
+ self.isin = isin
137
+ self.mf = self.model.objects.get(isin_number=self.isin)
138
+
139
+ def extract(self):
140
+
141
+ querystring = {"securityId": self.mf.security_id}
142
+
143
+ response = requests.get(
144
+ self.api_url, headers=settings.MORNINGSTAR_API_HEADERS, params=querystring
145
+ )
146
+
147
+ # Check if the request was successful (status code 200)
148
+ if response.status_code == 200:
149
+ # Parse JSON response
150
+ self.api_response = response.json()
151
+ else:
152
+ logger.info(f"received status code {response.status_code} for {self.isin}")
153
+ logger.debug(response.content)
154
+ response.raise_for_status()
155
+
156
+ def load(self):
157
+ self.mf.data.update({"holdings": self.transformed_data})
158
+ self.mf.save()
159
+ logger.info(f"Successfully stored data of holdings for {self.mf.fund_name}")
160
+
161
+
162
+ class MFRiskMeasures(DataClient):
163
+ model = MutualFund
164
+ api_url = (
165
+ f"https://{settings.MORNINGSTAR_HOST}/etf/risk/get-risk-volatility-measures"
166
+ )
167
+
168
+ def __init__(self, isin) -> None:
169
+ self.api_response = None
170
+ self.isin = isin
171
+ self.mf = self.model.objects.get(isin_number=self.isin)
172
+
173
+ def extract(self):
174
+
175
+ querystring = {"securityId": self.mf.security_id}
176
+
177
+ response = requests.get(
178
+ self.api_url, headers=settings.MORNINGSTAR_API_HEADERS, params=querystring
179
+ )
180
+
181
+ # Check if the request was successful (status code 200)
182
+ if response.status_code == 200:
183
+ # Parse JSON response
184
+ self.api_response = response.json()
185
+ else:
186
+ logger.info(response.json())
187
+ response.raise_for_status()
188
+
189
+ def load(self):
190
+ self.mf.data.update({"risk_measures": self.transformed_data})
191
+ self.mf.save()
192
+ logger.info(
193
+ f"Successfully stored data of risk measures for {self.mf.fund_name}"
194
+ )
195
+
196
+
197
+ class MFRanking(DataClient):
198
+
199
+ api_url = MONEYCONTROL_TOPFUNDS_URL
200
+ model = MutualFund
201
+
202
+ def __init__(self) -> None:
203
+ self.api_response = None
204
+ self.transformed_data = None
205
+
206
+ def extract(self) -> None:
207
+ """
208
+ Fetches the top mutual funds from MoneyControl website based on their returns and
209
+ returns a tuple containing lists of fund names, fund types, CRISIL ranks,
210
+ INF numbers, and AUM data of top mutual funds.
211
+ """
212
+ super().extract()
213
+
214
+ logger.info("Fetching top mutual funds from MoneyControl website")
215
+ response = requests.get(self.api_url)
216
+
217
+ # Check if the request was successful (status code 200)
218
+ response.raise_for_status()
219
+
220
+ soup = BeautifulSoup(response.text, "html.parser")
221
+
222
+ # Find all rows containing fund information
223
+ fund_rows = soup.find_all("tr", class_=lambda x: x and "INF" in x)
224
+ logger.info("Found %s rows", len(fund_rows))
225
+
226
+ fund_details = []
227
+
228
+ # Extract fund name from each row of sponsored funds
229
+ for row in fund_rows:
230
+ columns = row.find_all("td")
231
+ fund_name = columns[0].text.strip()
232
+ fund_type = columns[2].text.strip()
233
+ crisil_rank = columns[3].text.strip()
234
+ aum = columns[4].text.strip()
235
+ isin_number = row["class"][0]
236
+
237
+ fund_details.append(
238
+ {
239
+ "fund_name": fund_name,
240
+ "fund_type": fund_type,
241
+ "crisil_rank": crisil_rank,
242
+ "isin_number": isin_number,
243
+ "aum": aum,
244
+ }
245
+ )
246
+
247
+ self.api_response = fund_details
248
+
249
+ def load(self) -> None:
250
+ """
251
+ Load the data into the database
252
+ """
253
+
254
+ # clear the rank field
255
+ MutualFund.objects.exclude(rank=None).update(rank=None)
256
+
257
+ for rank, fund_details in enumerate(self.transformed_data, 1):
258
+ mf = MutualFund.objects.get(isin_number=fund_details["isin_number"])
259
+ mf.crisil_rank = (
260
+ fund_details["crisil_rank"] if fund_details["crisil_rank"] != "-" else 0
261
+ )
262
+ mf.rank = rank
263
+ mf.aum = float(fund_details["aum"].replace(",", ""))
264
+ mf.save()
265
+ logger.info(
266
+ f"Updated {rank=} {mf.fund_name} | {fund_details=} {fund_details['crisil_rank']=} {fund_details['aum']=}"
267
+ )
core/clients/stock.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This module contains the client classes for retrieving stock data from various sources.
3
+ """
4
+
5
+ import time
6
+ import logging
7
+
8
+ import requests
9
+ from bs4 import BeautifulSoup
10
+
11
+ from django.conf import settings
12
+ from core.models import Stock
13
+ from data_pipeline.interfaces.api_client import DataClient
14
+ from core.constants import MONEYCONTROL_TOPSTOCKS_URL
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class StockRankings(DataClient):
20
+ """ """
21
+
22
+ model = Stock
23
+ api_url = MONEYCONTROL_TOPSTOCKS_URL
24
+
25
+ def __init__(self) -> None:
26
+ self.api_response = None
27
+ self.transformed_data = None
28
+
29
+ def extract(self) -> None:
30
+
31
+ logger.info("Fetching data from %s", self.api_url)
32
+ with requests.Session() as session:
33
+ try:
34
+ response = session.get(self.api_url)
35
+ response.raise_for_status() # Raise an HTTPError for bad responses (4xx or 5xx)
36
+ except requests.exceptions.RequestException as e:
37
+ logger.error(f"Error fetching data from {self.api_url}: {e}")
38
+ return
39
+
40
+ soup = BeautifulSoup(response.text, "html.parser")
41
+
42
+ # Find the table containing stock information
43
+ table = soup.find("table", {"id": "indicesTable"})
44
+ if not table:
45
+ logger.warning(
46
+ "Table with id 'indicesTable' not found for stock during scraping ranks."
47
+ )
48
+ raise Exception(
49
+ "Table with id 'indicesTable' not found for stock during scraping ranks."
50
+ )
51
+
52
+ data = []
53
+ rows = table.find_all("tr")
54
+ logger.info(f"Found {len(rows)} rows in the table")
55
+ isin_fails = 0
56
+ # Extract data from each row
57
+ for idx, row in enumerate(rows, start=1):
58
+ columns = row.find_all("td")
59
+ isin_number = None
60
+ if columns:
61
+ link = columns[0].find("a").get("href") if columns[0] else None
62
+ if link is not None:
63
+ try:
64
+ logger.info(f"Fetching stock details from link {link}")
65
+ response = session.get(link)
66
+ time.sleep(2)
67
+ response.raise_for_status()
68
+ soup = BeautifulSoup(response.text, "html.parser")
69
+ isin_element = soup.select_one(
70
+ 'li.clearfix span:contains("ISIN") + p'
71
+ )
72
+ if isin_element:
73
+ isin_number = isin_element.get_text(strip=True)
74
+ else:
75
+ isin_fails += 1
76
+ logger.warning(f"ISIN not found for link {link}")
77
+ except requests.exceptions.RequestException as e:
78
+ logger.exception(
79
+ f"Error fetching ISIN from link {link}: {e}"
80
+ )
81
+ data.append(
82
+ {
83
+ "name": columns[0].get_text(strip=True),
84
+ "ltp": columns[1].get_text(strip=True),
85
+ "link": link,
86
+ "volume": columns[4].get_text(strip=True),
87
+ "percentage_change": columns[2].get_text(strip=True),
88
+ "price_change": columns[3].get_text(strip=True),
89
+ "rank": idx,
90
+ "isin_number": isin_number,
91
+ }
92
+ )
93
+ logger.info(f"ISIN not found for {isin_fails} stocks out of {len(rows)}")
94
+
95
+ self.api_response = data
96
+
97
+ def load(self) -> None:
98
+ """
99
+ Load the data into the database
100
+ """
101
+ logger.info("Loading ranking data into the database...")
102
+ # clear the rank field
103
+ Stock.objects.exclude(rank=None).update(rank=None)
104
+
105
+ for rank, stock_details in enumerate(self.transformed_data, 1):
106
+ try:
107
+ stock = Stock.objects.get(isin_number=stock_details["isin_number"])
108
+ except Stock.DoesNotExist:
109
+ logger.warning(
110
+ f"No matching stock found for ISIN: {stock_details['isin_number']} creating new object..."
111
+ )
112
+ stock = Stock.objects.create(data={"stock_rank": stock_details})
113
+
114
+ else:
115
+ stock.data.update({"stock_rank": stock_details})
116
+
117
+ stock.name = stock_details["name"]
118
+ stock.ltp = stock_details["ltp"]
119
+ stock.percentage_change = stock_details["percentage_change"]
120
+ stock.price_change = stock_details["price_change"]
121
+ stock.link = stock_details["link"]
122
+ stock.volume = stock_details["volume"]
123
+ stock.isin_number = stock_details["isin_number"]
124
+ stock.rank = stock_details["rank"]
125
+ stock.save()
126
+
127
+ logger.info(
128
+ f"Saved {rank=} {stock.name} | {stock_details=} {stock_details['isin_number']=}"
129
+ )
130
+
131
+
132
+ class StockDetails(DataClient):
133
+ """
134
+ Retrieves and updates stock details from the Morningstar API.
135
+ """
136
+
137
+ model = Stock
138
+ api_url = f"https://{settings.MORNINGSTAR_HOST}/stock/get-detail"
139
+
140
+ def __init__(self, perf_id: str, isin_number: str) -> None:
141
+ """
142
+ Initializes the StockDetails object.
143
+
144
+ Args:
145
+ perf_id (str): Performance ID of the stock.
146
+ isin_number (str): ISIN number of the stock.
147
+ """
148
+ if not perf_id:
149
+ raise ValueError("Performance ID cannot be empty.")
150
+ if not isin_number:
151
+ raise ValueError("ISIN number cannot be empty.")
152
+
153
+ self.api_response = {"details": None}
154
+ self.perf_id = perf_id
155
+ self.isin_number = isin_number
156
+
157
+ def _request(self) -> requests.Response:
158
+
159
+ querystring = {"PerformanceId": self.perf_id}
160
+ return requests.get(
161
+ self.api_url,
162
+ headers=settings.MORNINGSTAR_API_HEADERS,
163
+ params=querystring,
164
+ )
165
+
166
+ def extract(self) -> None:
167
+ """
168
+ Extracts stock details from the Morningstar API.
169
+ """
170
+
171
+ response = self._request()
172
+
173
+ requests_count = 1
174
+ while response.status_code != 200:
175
+ if response.status_code == 429:
176
+
177
+ logger.info(
178
+ f"API response: %s. Waiting for %s secs",
179
+ response.status_code,
180
+ 30 * requests_count,
181
+ )
182
+ time.sleep(30 * requests_count)
183
+ response = self._request()
184
+ if requests_count > 3:
185
+ logger.warning(
186
+ f"API response: %s. Max retries reached", response.status_code
187
+ )
188
+ break
189
+ requests_count += 1
190
+
191
+ else:
192
+ self.api_response["details"] = response.json()
193
+ logger.info(f"API response: %s", response.status_code)
194
+
195
+ def load(self) -> None:
196
+ """
197
+ Loads the retrieved stock details into the database.
198
+ """
199
+
200
+ stock = Stock.objects.filter(isin_number=self.isin_number).first()
201
+ if stock is None:
202
+ logger.warning(f"No matching stock found for ISIN: {self.isin_number}")
203
+ return
204
+ stock.data = self.transformed_data
205
+ stock.save()
206
+ logger.info(f"Successfully stored data for {stock.isin_number}.")
core/constants.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ MONEYCONTROL_TOPFUNDS_URL = "https://www.moneycontrol.com/mutual-funds/performance-tracker/returns/large-cap-fund.html"
2
+ MONEYCONTROL_TOPSTOCKS_URL = "https://www.moneycontrol.com/markets/indian-indices/changeTableData?deviceType=web&exName=N&indicesID=49&selTab=o&subTabOT=d&subTabOPL=cl&selPage=marketTerminal&classic=true"
3
+ TOPFUNDS_COUNT = 30
4
+ STOCKS_MAX_RANK = 1000
core/cron.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from data_pipeline import MFList
2
+
3
+
4
+ def store_mutual_funds():
5
+ mf_list = MFList()
6
+ mf_list.extract()
7
+ mf_list.transform()
8
+ mf_list.load()
9
+ print("Stored successfully")
core/management/commands/text2sql_eval.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import time
3
+ import csv
4
+ from django.core.management.base import BaseCommand
5
+ from core.text2sql.handler import QueryDataHandler
6
+ from core.text2sql.prompt import get_prompt
7
+ from core.text2sql.eval_queries import queries
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ class Command(BaseCommand):
12
+ help = "Morningstar API to save the JSON response to a file which contains secIds with other details"
13
+
14
+ def handle(self, *args, **options) -> None:
15
+ t1 = time.perf_counter()
16
+ q=[]
17
+ count =1
18
+ for query in queries[26:]:
19
+ print("count: ", query["Query Number"])
20
+ prompt = get_prompt(query["Query Description"])
21
+ logger.info(f"Prompt: {prompt}")
22
+ generated_query, data = QueryDataHandler().get_data_from_query(prompt)
23
+ print(f"Description: {query['Query Description']}, Query: {query.get('SQL Statement')}, Generated: {generated_query} ")
24
+ q.append({
25
+ "Query Number": query["Query Number"],
26
+ "Complexity Level": query["Complexity Level"],
27
+ "Description": query["Query Description"],
28
+ "Query": query.get("SQL Statement", "-"),
29
+ "Generated": generated_query,
30
+ })
31
+ count+=1
32
+ time.sleep(1)
33
+ csv_file_path = 'queries_data.csv'
34
+
35
+ # Writing data to CSV
36
+ with open(csv_file_path, 'w', newline='', encoding='utf-8') as csv_file:
37
+ fieldnames = q[0].keys()
38
+ print(fieldnames)
39
+ writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
40
+
41
+ # Write the header
42
+ writer.writeheader()
43
+
44
+ # Write the data
45
+ writer.writerows(q)
46
+
47
+ print(f'Data has been written to {csv_file_path}.')
48
+ self.stdout.write(f"Time taken for evaluation: {time.perf_counter() - t1}")
49
+
50
+
core/management/commands/update_mf.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This command is used to fetch all the data relating to mutual funds and save it to the database.
3
+ """
4
+
5
+ import logging
6
+ import time
7
+
8
+ from django.core.management.base import BaseCommand
9
+ from core.constants import MONEYCONTROL_TOPSTOCKS_URL
10
+ from core.models import MutualFund
11
+ from core.clients.mutual_fund import (
12
+ MFList,
13
+ MFQuote,
14
+ MFHoldings,
15
+ MFRiskMeasures,
16
+ MFRanking,
17
+ )
18
+ from core.clients.stock import StockDetails, StockRankings
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def get_funds_details() -> None:
24
+ """
25
+ Get the details of the top 30 mutual funds and store them in the database.
26
+ """
27
+
28
+ t1 = time.perf_counter()
29
+ mutual_funds = MutualFund.objects.exclude(rank=None).order_by("rank")[:30]
30
+ logger.info(f"{mutual_funds=}")
31
+ for mf in mutual_funds:
32
+ # fetching the quotes data from the morningstar api and storing it in the database
33
+ MFQuote(mf.isin_number).run()
34
+ time.sleep(2)
35
+ # fetching the holdings data from the morningstar api and storing it in the database
36
+ MFHoldings(mf.isin_number).run()
37
+ time.sleep(2)
38
+
39
+ # fetching the risk measures data from the morningstar api and storing it in the database
40
+ MFRiskMeasures(mf.isin_number).run()
41
+ time.sleep(2)
42
+
43
+ logger.info("Time taken: %s", time.perf_counter() - t1)
44
+
45
+
46
+ def get_stock_details() -> None:
47
+ """
48
+ Retrieves stock details for the top 30 mutual funds and updates the database.
49
+ """
50
+ count = 0
51
+ t1 = time.perf_counter()
52
+ mutual_funds = MutualFund.objects.exclude(rank=None).order_by("rank")[:30]
53
+
54
+ for mf in mutual_funds:
55
+
56
+ try:
57
+ holdings = (
58
+ mf.data["holdings"].get("equityHoldingPage", {}).get("holdingList", [])
59
+ )
60
+ except KeyError:
61
+ logger.warning("KeyError for holdings on Mutual Fund %s", mf.isin_number)
62
+
63
+ for holding in holdings:
64
+ performance_id = holding.get("performanceId")
65
+ isin = holding.get("isin")
66
+
67
+ if not performance_id or not isin:
68
+ logger.warning("Missing performanceId or isin for Mutual Fund %s", isin)
69
+ MFHoldings(mf.isin_number).run()
70
+
71
+ stock_details = StockDetails(performance_id, isin)
72
+ stock_details.run()
73
+ count += 1
74
+
75
+ logger.info("Processed count: %s", count)
76
+ logger.info("Time taken by stock details: %s", time.perf_counter() - t1)
77
+
78
+
79
+ class Command(BaseCommand):
80
+ help = "Morningstar API to save the JSON response to a file which contains secIds with other details"
81
+
82
+ def handle(self, *args, **options) -> None:
83
+ t1 = time.perf_counter()
84
+ try:
85
+ MFList().run()
86
+ MFRanking().run()
87
+ get_funds_details()
88
+ StockRankings().run()
89
+ get_stock_details()
90
+ except Exception as e:
91
+ logger.exception(e)
92
+ self.stdout.write(f"Time taken by: {time.perf_counter() - t1}")
core/mfrating/score_calculator.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This module defines a class, MFRating, which provides methods for calculating
3
+ the weighted rating and overall score for mutual funds based on various parameters.
4
+
5
+ """
6
+ import logging
7
+ from typing import List, Dict, Any
8
+ import numpy as np
9
+ from django.db.models import Max, Min
10
+ from core.models import MutualFund, Stock
11
+
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class MFRating:
17
+ """
18
+ This class provides methods for calculating the weighted stock rank rating and overall score for mutual funds based on various parameters.
19
+ """
20
+
21
+ def __init__(self, max_rank: int = 1000) -> None:
22
+ self.max_rank = max_rank
23
+ self.scores = {
24
+ "stock_ranking_score": [10],
25
+ "crisil_rank_score": [10],
26
+ "churn_score": [10],
27
+ "sharperatio_score": [10],
28
+ "expenseratio_score": [10],
29
+ "aum_score": [10],
30
+ "alpha_score": [10],
31
+ "beta_score": [10],
32
+ }
33
+
34
+ def get_weighted_score(self, values: List[float]) -> float:
35
+ """
36
+ Calculates the weighted rating based on the weights and values provided.
37
+ """
38
+ weights = []
39
+ values = []
40
+ for _, (weight, score) in self.scores.items():
41
+ weights.append(weight)
42
+ values.append(score)
43
+
44
+ return np.average(values, weights=weights)
45
+
46
+ def get_rank_rating(self, stock_ranks: List[int]) -> List[float]:
47
+ """
48
+ Calculates the rank rating based on the stock ranks and the maximum rank.
49
+ """
50
+ return [
51
+ (self.max_rank - (rank if rank else self.max_rank)) / self.max_rank
52
+ for rank in stock_ranks
53
+ ]
54
+
55
+ def get_overall_score(self, **kwargs) -> float:
56
+ """
57
+ It returns the overall weighted score for mutual funds based on various parameters.
58
+
59
+ """
60
+
61
+ stock_rankings = self.get_rank_rating(kwargs.get("stock_rankings"))
62
+ # what np.average do?
63
+ # Multiply each element in the stock_rankings array by its corresponding weights, then Sum up the results, then divide by the sum of the weights.
64
+ # data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
65
+ # weights = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
66
+ #
67
+ # Multiply each element in the data array by its corresponding weight:
68
+ # [1*10, 2*9, 3*8, 4*7, 5*6, 6*5, 7*4, 8*3, 9*2, 10*1]
69
+ # [10, 18, 24, 28, 30, 30, 28, 24, 18, 10]
70
+ #
71
+ # Sum up the results:
72
+ # 10 + 18 + 24 + 28 + 30 + 30 + 28 + 24 + 18 + 10 = 220
73
+ #
74
+ # Sum up the weights:
75
+ # 10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 = 55
76
+ #
77
+ # Divide the sum of the weighted elements by the sum of the weights:
78
+ # 220 / 55 = 4.0
79
+ self.scores["stock_ranking_score"].append(
80
+ np.average(stock_rankings, weights=kwargs.get("stock_weights"))
81
+ )
82
+ self.scores["alpha_score"].append(kwargs.get("alpha", 0) / 100)
83
+ self.scores["beta_score"].append((2 - kwargs.get("beta", 2)) / 2)
84
+ self.scores["crisil_rank_score"].append(
85
+ (kwargs.get("crisil_rank_score", 0)) / 5
86
+ )
87
+ self.scores["churn_score"].append(kwargs.get("churn_rate", 0) / 100)
88
+ self.scores["sharperatio_score"].append(kwargs.get("sharpe_ratio", 0) / 100)
89
+ self.scores["expenseratio_score"].append(kwargs.get("expense_ratio", 0) / 100)
90
+ max_aum, min_aum, aum = kwargs.get("aum_score", (1, 0, 0))
91
+ self.scores["aum_score"].append((aum - min_aum) / (max_aum - min_aum))
92
+ # Calculate the overall rating using weighted sum
93
+
94
+ return self.get_weighted_score(self.scores)
95
+
96
+
97
+ class MutualFundScorer:
98
+ def __init__(self) -> None:
99
+ self.mf_scores = []
100
+
101
+ def _get_stock_ranks(self, isin_ids: List[str]) -> List[int]:
102
+ """Get stock ranks based on ISIN ids."""
103
+
104
+ return list(
105
+ Stock.objects.filter(isin_number__in=isin_ids)
106
+ .order_by("rank")
107
+ .values_list("rank", "isin_number")
108
+ )
109
+
110
+ def _get_mutual_funds(self) -> List[MutualFund]:
111
+ """Get a list of top 30 mutual funds based on rank."""
112
+
113
+ return MutualFund.objects.exclude(rank=None).order_by("rank")[:30]
114
+
115
+ def _get_risk_measure(
116
+ self, risk_measures: Dict[str, Any], key: str, year: str
117
+ ) -> float:
118
+ """
119
+ Get value of the specified key from the risk_measures dictionary for the given year.
120
+ """
121
+ try:
122
+ value = risk_measures.get(year, {}).get(key, 0)
123
+ return float(value)
124
+ except (TypeError, ValueError):
125
+ return 0
126
+
127
+ def _get_most_non_null_key(self, key, mutual_funds):
128
+ """
129
+ Get the year with the maximum number of non-None values for the specified key
130
+ within the given mutual funds.
131
+ """
132
+ year_counts = {
133
+ "for15Year": 0,
134
+ "for10Year": 0,
135
+ "for5Year": 0,
136
+ "for3Year": 0,
137
+ "for1Year": 0,
138
+ }
139
+
140
+ for mf in mutual_funds:
141
+ risk_measures = mf.data["risk_measures"].get("fundRiskVolatility", {})
142
+
143
+ for year in year_counts:
144
+ if risk_measures.get(year, {}).get(key) is not None:
145
+ year_counts[year] += 1
146
+
147
+ most_non_null_year = max(year_counts, key=year_counts.get)
148
+ return most_non_null_year
149
+
150
+ def get_scores(self) -> List[Dict[str, Any]]:
151
+ """Calculate scores for mutual funds and return the results."""
152
+
153
+ logger.info("Calculating scores for mutual funds...")
154
+ max_aum = MutualFund.objects.exclude(rank=None).aggregate(max_price=Max("aum"))[
155
+ "max_price"
156
+ ]
157
+ min_aum = MutualFund.objects.exclude(rank=None).aggregate(min_price=Min("aum"))[
158
+ "min_price"
159
+ ]
160
+ mutual_funds = self._get_mutual_funds()
161
+
162
+ # Get the year with the maximum number of non-None values for sharpeRatio, alpha and beta
163
+ sharpe_ratio_year = self._get_most_non_null_key("sharpeRatio", mutual_funds)
164
+ alpha_year = self._get_most_non_null_key("alpha", mutual_funds)
165
+ beta_year = self._get_most_non_null_key("beta", mutual_funds)
166
+ for mf in mutual_funds:
167
+ mf_rating = MFRating(
168
+ max_rank=1000,
169
+ )
170
+ logger.info(f"Processing mutual fund: %s", mf.fund_name)
171
+ holdings = (
172
+ mf.data.get("holdings", {})
173
+ .get("equityHoldingPage", {})
174
+ .get("holdingList", [])
175
+ )
176
+ portfolio_holding_weights = {
177
+ holding.get("isin"): (
178
+ holding.get("weighting") if holding.get("weighting") else 0
179
+ )
180
+ for holding in holdings
181
+ if holding.get("isin")
182
+ }
183
+ stock_ranks_and_weights = [
184
+ (rank, portfolio_holding_weights[isin])
185
+ for rank, isin in self._get_stock_ranks(
186
+ portfolio_holding_weights.keys()
187
+ )
188
+ ]
189
+ stock_ranks, stock_weights = zip(*stock_ranks_and_weights)
190
+ sharpe_ratio = self._get_risk_measure(
191
+ mf.data["risk_measures"].get("fundRiskVolatility", {}),
192
+ "sharpeRatio",
193
+ sharpe_ratio_year,
194
+ )
195
+ alpha = self._get_risk_measure(
196
+ mf.data["risk_measures"].get("fundRiskVolatility", {}),
197
+ "alpha",
198
+ alpha_year,
199
+ )
200
+ beta = self._get_risk_measure(
201
+ mf.data["risk_measures"].get("fundRiskVolatility", {}),
202
+ "beta",
203
+ beta_year,
204
+ )
205
+ overall_score = mf_rating.get_overall_score(
206
+ stock_rankings=stock_ranks,
207
+ stock_weights=stock_weights,
208
+ churn_rate=mf.data["quotes"]["lastTurnoverRatio"]
209
+ if mf.data["quotes"].get("lastTurnoverRatio")
210
+ else 0,
211
+ sharpe_ratio=sharpe_ratio,
212
+ expense_ratio=mf.data["quotes"]["expenseRatio"],
213
+ crisil_rank_score=mf.crisil_rank,
214
+ aum_score=(max_aum, min_aum, mf.aum),
215
+ alpha=alpha,
216
+ beta=beta,
217
+ )
218
+
219
+ self.mf_scores.append(
220
+ {
221
+ "isin": mf.isin_number,
222
+ "name": mf.fund_name,
223
+ "rank": mf.rank,
224
+ "sharpe_ratio": round(sharpe_ratio, 4),
225
+ "churn_rate": mf.data["quotes"].get("lastTurnoverRatio", 0),
226
+ "expense_ratio": mf.data["quotes"].get("expenseRatio", 0),
227
+ "aum": mf.aum,
228
+ "alpha": round(alpha, 4),
229
+ "beta": round(beta, 4),
230
+ "crisil_rank": mf.crisil_rank,
231
+ "overall_score": round(overall_score, 4),
232
+ }
233
+ )
234
+ logger.info("Finished calculating scores.")
235
+ return sorted(self.mf_scores, key=lambda d: d["overall_score"], reverse=True)
core/middleware.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import traceback
3
+
4
+ from django.http import JsonResponse
5
+
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class ExceptionMiddleware:
11
+ """
12
+ Middleware to catch exceptions and handle them with appropriate logging and JSON response.
13
+ """
14
+
15
+ def __init__(self, get_response):
16
+ """
17
+ Initializes the ExceptionMiddleware with the provided get_response function.
18
+ """
19
+ self.get_response = get_response
20
+
21
+ def __call__(self, request):
22
+ """
23
+ Process the request and call the next middleware or view function in the chain.
24
+ """
25
+ response = self.get_response(request)
26
+ return response
27
+
28
+ def process_exception(self, request, exception):
29
+ """
30
+ Called when a view function raises an exception.
31
+ """
32
+ error_type = exception.__class__.__name__
33
+ error_message = exception.args
34
+ logger.info(f"Error Type: {error_type} | Error Message: {error_message}")
35
+ logger.debug("Request Details: %s", request.__dict__)
36
+ logger.exception(traceback.format_exc())
37
+
38
+ if isinstance(exception, KeyError):
39
+ status_code = 400
40
+ message = f"Please Add Valid Data For {error_message[0]}"
41
+ error = "BAD_REQUEST"
42
+ elif isinstance(exception, AttributeError):
43
+ status_code = 500
44
+ message = "Something Went Wrong. Please try again."
45
+ error = "SOMETHING_WENT_WRONG"
46
+ elif isinstance(exception, TypeError):
47
+ status_code = 500
48
+ message = "Something Went Wrong. Please try again."
49
+ error = "SOMETHING_WENT_WRONG"
50
+ else:
51
+ status_code = 500
52
+ message = "Something Went Wrong. Please try again."
53
+ error = "SOMETHING_WENT_WRONG"
54
+
55
+ return JsonResponse({"message": message, "error": error}, status=status_code)
core/migrations/0001_initial.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2023-12-05 10:58
2
+
3
+ from django.db import migrations, models
4
+ import uuid
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ initial = True
10
+
11
+ dependencies = []
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name="MutualFund",
16
+ fields=[
17
+ ("created_at", models.DateTimeField(auto_now_add=True, null=True)),
18
+ ("updated_at", models.DateTimeField(auto_now=True, null=True)),
19
+ (
20
+ "id",
21
+ models.UUIDField(
22
+ default=uuid.uuid4,
23
+ editable=False,
24
+ primary_key=True,
25
+ serialize=False,
26
+ ),
27
+ ),
28
+ ("fund_house", models.CharField(max_length=200)),
29
+ (
30
+ "isin_number",
31
+ models.CharField(max_length=50, null=True, unique=True),
32
+ ),
33
+ ("security_id", models.CharField(max_length=50, unique=True)),
34
+ ("data", models.JSONField(null=True)),
35
+ ("rank", models.IntegerField(null=True, unique=True)),
36
+ ],
37
+ options={
38
+ "abstract": False,
39
+ },
40
+ ),
41
+ ]
core/migrations/0002_rename_fund_house_mutualfund_fund_name.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2023-12-06 05:44
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("core", "0001_initial"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.RenameField(
14
+ model_name="mutualfund",
15
+ old_name="fund_house",
16
+ new_name="fund_name",
17
+ ),
18
+ ]
core/migrations/0002_stock.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2023-12-06 13:59
2
+
3
+ from django.db import migrations, models
4
+ import uuid
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ("core", "0001_initial"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name="Stock",
16
+ fields=[
17
+ ("created_at", models.DateTimeField(auto_now_add=True, null=True)),
18
+ ("updated_at", models.DateTimeField(auto_now=True, null=True)),
19
+ (
20
+ "id",
21
+ models.UUIDField(
22
+ default=uuid.uuid4,
23
+ editable=False,
24
+ primary_key=True,
25
+ serialize=False,
26
+ ),
27
+ ),
28
+ ("name", models.CharField(max_length=200)),
29
+ ("ltp", models.CharField(max_length=50, null=True)),
30
+ ("percentage_change", models.CharField(max_length=50, null=True)),
31
+ ("price_change", models.CharField(max_length=50, null=True)),
32
+ ("link", models.URLField(max_length=50, null=True)),
33
+ ("volume", models.CharField(max_length=50, null=True)),
34
+ ("data", models.JSONField(null=True)),
35
+ ("rank", models.IntegerField(null=True, unique=True)),
36
+ ],
37
+ options={
38
+ "abstract": False,
39
+ },
40
+ ),
41
+ ]
core/migrations/0003_alter_mutualfund_fund_name.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2023-12-12 11:29
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("core", "0002_rename_fund_house_mutualfund_fund_name"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name="mutualfund",
15
+ name="fund_name",
16
+ field=models.CharField(max_length=200, unique=True),
17
+ ),
18
+ ]
core/migrations/0003_alter_stock_link.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2023-12-06 14:07
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("core", "0002_stock"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name="stock",
15
+ name="link",
16
+ field=models.URLField(null=True),
17
+ ),
18
+ ]
core/migrations/0004_stock_isin_number.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2023-12-07 06:26
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("core", "0003_alter_stock_link"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name="stock",
15
+ name="isin_number",
16
+ field=models.CharField(max_length=50, null=True, unique=True),
17
+ ),
18
+ ]
core/migrations/0005_merge_20231211_0610.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2023-12-11 06:10
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("core", "0002_rename_fund_house_mutualfund_fund_name"),
10
+ ("core", "0004_stock_isin_number"),
11
+ ]
12
+
13
+ operations = []
core/migrations/0006_mutualfund_crisil_rank.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2023-12-14 12:15
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("core", "0005_merge_20231211_0610"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name="mutualfund",
15
+ name="crisil_rank",
16
+ field=models.IntegerField(null=True),
17
+ )
18
+ ]
core/migrations/0007_merge_20231214_1924.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2023-12-14 19:24
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("core", "0003_alter_mutualfund_fund_name"),
10
+ ("core", "0006_mutualfund_crisil_rank"),
11
+ ]
12
+
13
+ operations = []
core/migrations/0008_mutualfund_aum.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2023-12-14 19:24
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("core", "0007_merge_20231214_1924"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name="mutualfund",
15
+ name="aum",
16
+ field=models.FloatField(null=True),
17
+ ),
18
+ ]
core/migrations/0009_mutualfund_expense_ratio_mutualfund_return_m12_and_more.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2024-01-09 12:19
2
+
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+ import uuid
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ ("core", "0008_mutualfund_aum"),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name="mutualfund",
17
+ name="expense_ratio",
18
+ field=models.FloatField(blank=True, null=True),
19
+ ),
20
+ migrations.AddField(
21
+ model_name="mutualfund",
22
+ name="return_m12",
23
+ field=models.FloatField(blank=True, null=True),
24
+ ),
25
+ migrations.CreateModel(
26
+ name="MFVolatility",
27
+ fields=[
28
+ (
29
+ "id",
30
+ models.UUIDField(
31
+ default=uuid.uuid4,
32
+ editable=False,
33
+ primary_key=True,
34
+ serialize=False,
35
+ ),
36
+ ),
37
+ (
38
+ "year",
39
+ models.CharField(
40
+ choices=[
41
+ (1, "for1Year"),
42
+ (3, "for3Year"),
43
+ (5, "for5Year"),
44
+ (10, "for10Year"),
45
+ (15, "for15Year"),
46
+ ],
47
+ max_length=100,
48
+ ),
49
+ ),
50
+ ("alpha", models.FloatField(blank=True, null=True)),
51
+ ("beta", models.FloatField(blank=True, null=True)),
52
+ ("sharpe_ratio", models.FloatField(blank=True, null=True)),
53
+ ("standard_deviation", models.FloatField(blank=True, null=True)),
54
+ (
55
+ "mutual_fund",
56
+ models.ForeignKey(
57
+ on_delete=django.db.models.deletion.CASCADE,
58
+ to="core.mutualfund",
59
+ ),
60
+ ),
61
+ ],
62
+ ),
63
+ migrations.CreateModel(
64
+ name="MFHoldings",
65
+ fields=[
66
+ (
67
+ "id",
68
+ models.UUIDField(
69
+ default=uuid.uuid4,
70
+ editable=False,
71
+ primary_key=True,
72
+ serialize=False,
73
+ ),
74
+ ),
75
+ ("isin_number", models.CharField(max_length=20)),
76
+ ("security_id", models.CharField(max_length=20)),
77
+ ("sector", models.CharField(max_length=50)),
78
+ ("country", models.CharField(max_length=50)),
79
+ ("currency", models.CharField(max_length=20)),
80
+ ("weighting", models.FloatField(blank=True, null=True)),
81
+ ("sector_code", models.CharField(max_length=20)),
82
+ ("holding_type", models.CharField(max_length=20)),
83
+ ("market_value", models.FloatField(blank=True, null=True)),
84
+ (
85
+ "stock_rating",
86
+ models.CharField(blank=True, max_length=10, null=True),
87
+ ),
88
+ ("total_assets", models.FloatField(blank=True, null=True)),
89
+ ("currency_name", models.CharField(max_length=50)),
90
+ ("maturity_date", models.DateField(blank=True, null=True)),
91
+ ("security_name", models.CharField(max_length=100)),
92
+ ("security_type", models.CharField(max_length=10)),
93
+ ("holding_type_id", models.CharField(max_length=1)),
94
+ ("number_of_shares", models.FloatField(blank=True, null=True)),
95
+ ("one_year_return", models.FloatField(blank=True, null=True)),
96
+ ("nav", models.FloatField(blank=True, null=True)),
97
+ (
98
+ "mutual_fund",
99
+ models.ForeignKey(
100
+ on_delete=django.db.models.deletion.CASCADE,
101
+ to="core.mutualfund",
102
+ ),
103
+ ),
104
+ ],
105
+ ),
106
+ ]
core/migrations/0010_remove_mfholdings_nav_mutualfund_nav.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2024-01-09 12:24
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("core", "0009_mutualfund_expense_ratio_mutualfund_return_m12_and_more"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.RemoveField(
14
+ model_name="mfholdings",
15
+ name="nav",
16
+ ),
17
+ migrations.AddField(
18
+ model_name="mutualfund",
19
+ name="nav",
20
+ field=models.FloatField(blank=True, null=True),
21
+ ),
22
+ ]
core/migrations/0011_remove_mfholdings_maturity_date_and_more.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2024-01-09 12:49
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("core", "0010_remove_mfholdings_nav_mutualfund_nav"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.RemoveField(
14
+ model_name="mfholdings",
15
+ name="maturity_date",
16
+ ),
17
+ migrations.AlterField(
18
+ model_name="mfholdings",
19
+ name="holding_type_id",
20
+ field=models.CharField(max_length=10),
21
+ ),
22
+ ]
core/migrations/0012_alter_mfholdings_isin_number.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2024-01-09 13:06
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("core", "0011_remove_mfholdings_maturity_date_and_more"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name="mfholdings",
15
+ name="isin_number",
16
+ field=models.CharField(blank=True, max_length=20, null=True),
17
+ ),
18
+ ]
core/migrations/0013_alter_mfholdings_country_alter_mfholdings_currency_and_more.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2024-01-09 13:08
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("core", "0012_alter_mfholdings_isin_number"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name="mfholdings",
15
+ name="country",
16
+ field=models.CharField(blank=True, max_length=50, null=True),
17
+ ),
18
+ migrations.AlterField(
19
+ model_name="mfholdings",
20
+ name="currency",
21
+ field=models.CharField(blank=True, max_length=20, null=True),
22
+ ),
23
+ migrations.AlterField(
24
+ model_name="mfholdings",
25
+ name="currency_name",
26
+ field=models.CharField(blank=True, max_length=50, null=True),
27
+ ),
28
+ migrations.AlterField(
29
+ model_name="mfholdings",
30
+ name="holding_type",
31
+ field=models.CharField(blank=True, max_length=20, null=True),
32
+ ),
33
+ migrations.AlterField(
34
+ model_name="mfholdings",
35
+ name="holding_type_id",
36
+ field=models.CharField(blank=True, max_length=10, null=True),
37
+ ),
38
+ migrations.AlterField(
39
+ model_name="mfholdings",
40
+ name="sector",
41
+ field=models.CharField(blank=True, max_length=50, null=True),
42
+ ),
43
+ migrations.AlterField(
44
+ model_name="mfholdings",
45
+ name="sector_code",
46
+ field=models.CharField(blank=True, max_length=20, null=True),
47
+ ),
48
+ migrations.AlterField(
49
+ model_name="mfholdings",
50
+ name="security_id",
51
+ field=models.CharField(blank=True, max_length=20, null=True),
52
+ ),
53
+ migrations.AlterField(
54
+ model_name="mfholdings",
55
+ name="security_name",
56
+ field=models.CharField(blank=True, max_length=100, null=True),
57
+ ),
58
+ migrations.AlterField(
59
+ model_name="mfholdings",
60
+ name="security_type",
61
+ field=models.CharField(blank=True, max_length=10, null=True),
62
+ ),
63
+ ]
core/migrations/0014_alter_mfholdings_currency_and_more.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2024-01-09 13:09
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("core", "0013_alter_mfholdings_country_alter_mfholdings_currency_and_more"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name="mfholdings",
15
+ name="currency",
16
+ field=models.CharField(blank=True, max_length=100, null=True),
17
+ ),
18
+ migrations.AlterField(
19
+ model_name="mfholdings",
20
+ name="currency_name",
21
+ field=models.CharField(blank=True, max_length=150, null=True),
22
+ ),
23
+ migrations.AlterField(
24
+ model_name="mfholdings",
25
+ name="holding_type",
26
+ field=models.CharField(blank=True, max_length=100, null=True),
27
+ ),
28
+ migrations.AlterField(
29
+ model_name="mfholdings",
30
+ name="holding_type_id",
31
+ field=models.CharField(blank=True, max_length=100, null=True),
32
+ ),
33
+ migrations.AlterField(
34
+ model_name="mfholdings",
35
+ name="sector_code",
36
+ field=models.CharField(blank=True, max_length=100, null=True),
37
+ ),
38
+ migrations.AlterField(
39
+ model_name="mfholdings",
40
+ name="security_type",
41
+ field=models.CharField(blank=True, max_length=100, null=True),
42
+ ),
43
+ migrations.AlterField(
44
+ model_name="mfholdings",
45
+ name="stock_rating",
46
+ field=models.CharField(blank=True, max_length=100, null=True),
47
+ ),
48
+ ]
core/migrations/0015_rename_security_name_mfholdings_holding_name_and_more.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2 on 2024-01-09 14:10
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("core", "0014_alter_mfholdings_currency_and_more"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.RenameField(
14
+ model_name="mfholdings",
15
+ old_name="security_name",
16
+ new_name="holding_name",
17
+ ),
18
+ migrations.RemoveField(
19
+ model_name="mfholdings",
20
+ name="security_type",
21
+ ),
22
+ ]
core/migrations/__init__.py ADDED
File without changes
core/models.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uuid
2
+ from django.db import models, connection
3
+
4
+
5
+ class BaseModel(models.Model):
6
+
7
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
8
+ created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
9
+ updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)
10
+
11
+ class Meta:
12
+ abstract = True
13
+
14
+
15
+ class MutualFund(BaseModel):
16
+ """
17
+ This model will store the mutual fund data
18
+ """
19
+
20
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
21
+ fund_name = models.CharField(max_length=200, unique=True)
22
+ isin_number = models.CharField(max_length=50, unique=True, null=True)
23
+ security_id = models.CharField(max_length=50, unique=True)
24
+ data = models.JSONField(null=True)
25
+ rank = models.IntegerField(unique=True, null=True)
26
+ crisil_rank = models.IntegerField(null=True)
27
+ aum = models.FloatField(null=True)
28
+ expense_ratio = models.FloatField(null=True, blank=True)
29
+ return_m12 = models.FloatField(null=True, blank=True)
30
+ nav = models.FloatField(null=True, blank=True)
31
+
32
+ @staticmethod
33
+ def execute_raw_sql_query(sql_query):
34
+ with connection.cursor() as cursor:
35
+ cursor.execute(sql_query)
36
+ columns = [col[0] for col in cursor.description]
37
+ results = [dict(zip(columns, row)) for row in cursor.fetchall()]
38
+
39
+ return results
40
+
41
+ @classmethod
42
+ def execute_query(cls, query):
43
+ try:
44
+ return cls.execute_raw_sql_query(query)
45
+ except Exception as e:
46
+ return []
47
+
48
+ class Stock(BaseModel):
49
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
50
+ name = models.CharField(max_length=200)
51
+ ltp = models.CharField(max_length=50, null=True)
52
+ percentage_change = models.CharField(max_length=50, null=True)
53
+ price_change = models.CharField(max_length=50, null=True)
54
+ link = models.URLField(max_length=200, null=True)
55
+ volume = models.CharField(max_length=50, null=True)
56
+ data = models.JSONField(null=True)
57
+ isin_number = models.CharField(max_length=50, unique=True, null=True)
58
+ rank = models.IntegerField(unique=True, null=True)
59
+
60
+
61
+ class MFHoldings(models.Model):
62
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
63
+ isin_number = models.CharField(max_length=20, null=True, blank=True)
64
+ security_id = models.CharField(max_length=20, null=True, blank=True)
65
+ sector = models.CharField(max_length=50, null=True, blank=True)
66
+ country = models.CharField(max_length=50, null=True, blank=True)
67
+ currency = models.CharField(max_length=100, null=True, blank=True)
68
+ weighting = models.FloatField(null=True, blank=True)
69
+ sector_code = models.CharField(max_length=100, null=True, blank=True)
70
+ holding_type = models.CharField(max_length=100, null=True, blank=True)
71
+ market_value = models.FloatField(null=True, blank=True)
72
+ stock_rating = models.CharField(max_length=100, null=True, blank=True)
73
+ total_assets = models.FloatField(null=True, blank=True)
74
+ currency_name = models.CharField(max_length=150, null=True, blank=True)
75
+ holding_name = models.CharField(max_length=100, null=True, blank=True)
76
+ holding_type = models.CharField(max_length=100, null=True, blank=True)
77
+ holding_type_id = models.CharField(max_length=100, null=True, blank=True)
78
+ number_of_shares = models.FloatField(null=True, blank=True)
79
+ one_year_return = models.FloatField(null=True, blank=True)
80
+ mutual_fund = models.ForeignKey(MutualFund, on_delete=models.CASCADE)
81
+
82
+ def __str__(self):
83
+ return f"{self.ticker} - {self.securityName}"
84
+
85
+
86
+ class MFVolatility(models.Model):
87
+ VOLATILITY_CHOICES = (
88
+ (1, "for1Year"),
89
+ (3, "for3Year"),
90
+ (5, "for5Year"),
91
+ (10, "for10Year"),
92
+ (15, "for15Year"),
93
+ )
94
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
95
+ mutual_fund = models.ForeignKey(MutualFund, on_delete=models.CASCADE)
96
+ year = models.CharField(max_length=100, choices=VOLATILITY_CHOICES)
97
+ alpha = models.FloatField(null=True, blank=True)
98
+ beta = models.FloatField(null=True, blank=True)
99
+ sharpe_ratio = models.FloatField(null=True, blank=True)
100
+ standard_deviation = models.FloatField(null=True, blank=True)
core/test_models.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.test import TestCase
2
+ from core.models import MutualFund
3
+
4
+
5
+ class TestMutualFund(TestCase):
6
+ def test_model_creation(self):
7
+ # Create a new MutualFund instance
8
+ mutual_fund1 = MutualFund(
9
+ fund_name="Test Fund 1",
10
+ isin_number="123456789012",
11
+ security_id="MST01234",
12
+ data={
13
+ "details": {
14
+ "legalName": "Test Fund 1",
15
+ "isin": "123456789012",
16
+ "secId": "MST01234",
17
+ }
18
+ },
19
+ )
20
+
21
+ # Save the MutualFund instance to the database
22
+ mutual_fund1.save()
23
+
24
+ # Check if the MutualFund instance was saved successfully
25
+ self.assertEqual(MutualFund.objects.count(), 1)
26
+
27
+ mutual_fund2 = MutualFund(
28
+ fund_name="Test Fund 2",
29
+ isin_number="9876543210",
30
+ security_id="MST56789",
31
+ data={
32
+ "details": {
33
+ "legalName": "Test Fund 2",
34
+ "isin": "9876543210",
35
+ "secId": "MST56789",
36
+ }
37
+ },
38
+ )
39
+ mutual_fund2.save()
40
+
41
+ self.assertNotEqual(mutual_fund1.id, mutual_fund2.id)
42
+ self.assertEqual(MutualFund.objects.count(), 2)
core/tests/__init__.py ADDED
File without changes
core/tests/data.py ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ test_data = {
2
+ 1: {
3
+ "quotes": {"expenseRatio": 0.023399999999999997, "lastTurnoverRatio": 0.1568},
4
+ "holdings": {
5
+ "equityHoldingPage": {
6
+ "pageSize": 100,
7
+ "totalPage": 1,
8
+ "pageNumber": 1,
9
+ "holdingList": [
10
+ {
11
+ "isin": "INE280A01028",
12
+ "weighting": 9.3,
13
+ },
14
+ {
15
+ "isin": "INE002A01018",
16
+ "weighting": 7.1,
17
+ },
18
+ {"isin": "INE090A01021", "weighting": 2.4},
19
+ {"isin": "INE040A01034", "weighting": 1.1},
20
+ ],
21
+ },
22
+ },
23
+ "list_info": {"isin": "MF123"},
24
+ "risk_measures": {
25
+ "cur": "INR",
26
+ "fundName": "Testing fund 2",
27
+ "indexName": "Morningstar India GR INR",
28
+ "categoryName": "Large-Cap",
29
+ "fundRiskVolatility": {
30
+ "endDate": "2023-11-30T06:00:00.000",
31
+ "for1Year": {
32
+ "beta": 1.002,
33
+ "alpha": 4.464,
34
+ "rSquared": 98.8,
35
+ "sharpeRatio": 0.877,
36
+ "standardDeviation": 11.355,
37
+ },
38
+ "for3Year": {
39
+ "beta": None,
40
+ "alpha": None,
41
+ "rSquared": None,
42
+ "sharpeRatio": None,
43
+ "standardDeviation": None,
44
+ },
45
+ "for5Year": {
46
+ "beta": None,
47
+ "alpha": None,
48
+ "rSquared": None,
49
+ "sharpeRatio": None,
50
+ "standardDeviation": None,
51
+ },
52
+ "for10Year": {
53
+ "beta": None,
54
+ "alpha": None,
55
+ "rSquared": None,
56
+ "sharpeRatio": None,
57
+ "standardDeviation": None,
58
+ },
59
+ "for15Year": {
60
+ "beta": None,
61
+ "alpha": None,
62
+ "rSquared": None,
63
+ "sharpeRatio": None,
64
+ "standardDeviation": None,
65
+ },
66
+ "bestFitIndexName": None,
67
+ "forLongestTenure": None,
68
+ "bestFitBetaFor3Year": None,
69
+ "primaryIndexNameNew": "S&P BSE 100 India TR INR",
70
+ "bestFitAlphaFor3Year": None,
71
+ "bestFitRSquaredFor3Year": None,
72
+ },
73
+ },
74
+ },
75
+ 2: {
76
+ "quotes": {"expenseRatio": 0.023399999999999997, "lastTurnoverRatio": 0.5158},
77
+ "holdings": {
78
+ "equityHoldingPage": {
79
+ "pageSize": 100,
80
+ "totalPage": 1,
81
+ "pageNumber": 1,
82
+ "holdingList": [
83
+ {"isin": "INE040A01034", "weighting": 7.6},
84
+ {"isin": "INE154A01025", "weighting": 1.1},
85
+ {"isin": "INE018A01030", "weighting": 3.2},
86
+ {"isin": "INE154A01025", "weighting": 3.1},
87
+ ],
88
+ },
89
+ },
90
+ "list_info": {"isin": "MF123"},
91
+ "risk_measures": {
92
+ "cur": "INR",
93
+ "fundName": "Testing fund 2",
94
+ "indexName": "Morningstar India GR INR",
95
+ "categoryName": "Large-Cap",
96
+ "fundRiskVolatility": {
97
+ "endDate": "2023-11-30T06:00:00.000",
98
+ "for1Year": {
99
+ "beta": 0.994,
100
+ "alpha": 0.337,
101
+ "rSquared": 98.8,
102
+ "sharpeRatio": 0.341,
103
+ "standardDeviation": 11.355,
104
+ },
105
+ "for3Year": {
106
+ "beta": None,
107
+ "alpha": None,
108
+ "rSquared": None,
109
+ "sharpeRatio": None,
110
+ "standardDeviation": None,
111
+ },
112
+ "for5Year": {
113
+ "beta": None,
114
+ "alpha": None,
115
+ "rSquared": None,
116
+ "sharpeRatio": None,
117
+ "standardDeviation": None,
118
+ },
119
+ "for10Year": {
120
+ "beta": None,
121
+ "alpha": None,
122
+ "rSquared": None,
123
+ "sharpeRatio": None,
124
+ "standardDeviation": None,
125
+ },
126
+ "for15Year": {
127
+ "beta": None,
128
+ "alpha": None,
129
+ "rSquared": None,
130
+ "sharpeRatio": None,
131
+ "standardDeviation": None,
132
+ },
133
+ "bestFitIndexName": None,
134
+ "forLongestTenure": None,
135
+ "bestFitBetaFor3Year": None,
136
+ "primaryIndexNameNew": "S&P BSE 100 India TR INR",
137
+ "bestFitAlphaFor3Year": None,
138
+ "bestFitRSquaredFor3Year": None,
139
+ },
140
+ },
141
+ },
142
+ 3: {
143
+ "quotes": {"expenseRatio": 0.0106, "lastTurnoverRatio": 0.3109},
144
+ "holdings": {
145
+ "equityHoldingPage": {
146
+ "pageSize": 100,
147
+ "totalPage": 1,
148
+ "pageNumber": 1,
149
+ "holdingList": [
150
+ {"isin": "INE280A01028", "weighting": 11.2},
151
+ {"isin": "INE090A01021", "weighting": 7.1},
152
+ {"isin": "INE040A01034", "weighting": 2.4},
153
+ ],
154
+ },
155
+ },
156
+ "list_info": {"isin": "MF123"},
157
+ "risk_measures": {
158
+ "cur": "INR",
159
+ "fundName": "Testing fund 2",
160
+ "indexName": "Morningstar India GR INR",
161
+ "categoryName": "Large-Cap",
162
+ "fundRiskVolatility": {
163
+ "endDate": "2023-11-30T06:00:00.000",
164
+ "for1Year": {
165
+ "beta": 0.954,
166
+ "alpha": -0.645,
167
+ "rSquared": 98.8,
168
+ "sharpeRatio": 0.251,
169
+ "standardDeviation": 11.355,
170
+ },
171
+ "for3Year": {
172
+ "beta": None,
173
+ "alpha": None,
174
+ "rSquared": None,
175
+ "sharpeRatio": None,
176
+ "standardDeviation": None,
177
+ },
178
+ "for5Year": {
179
+ "beta": None,
180
+ "alpha": None,
181
+ "rSquared": None,
182
+ "sharpeRatio": None,
183
+ "standardDeviation": None,
184
+ },
185
+ "for10Year": {
186
+ "beta": None,
187
+ "alpha": None,
188
+ "rSquared": None,
189
+ "sharpeRatio": None,
190
+ "standardDeviation": None,
191
+ },
192
+ "for15Year": {
193
+ "beta": None,
194
+ "alpha": None,
195
+ "rSquared": None,
196
+ "sharpeRatio": None,
197
+ "standardDeviation": None,
198
+ },
199
+ "bestFitIndexName": None,
200
+ "forLongestTenure": None,
201
+ "bestFitBetaFor3Year": None,
202
+ "primaryIndexNameNew": "S&P BSE 100 India TR INR",
203
+ "bestFitAlphaFor3Year": None,
204
+ "bestFitRSquaredFor3Year": None,
205
+ },
206
+ },
207
+ },
208
+ 4: {
209
+ "quotes": {"expenseRatio": 0.0158, "lastTurnoverRatio": 0.5451},
210
+ "holdings": {
211
+ "equityHoldingPage": {
212
+ "pageSize": 100,
213
+ "totalPage": 1,
214
+ "pageNumber": 1,
215
+ "holdingList": [
216
+ {"isin": "INE280A01028", "weighting": 10.2},
217
+ {"isin": "INE002A01018", "weighting": 9.2},
218
+ ],
219
+ },
220
+ },
221
+ "list_info": {"isin": "MF123"},
222
+ "risk_measures": {
223
+ "cur": "INR",
224
+ "fundName": "Testing fund 2",
225
+ "indexName": "Morningstar India GR INR",
226
+ "categoryName": "Large-Cap",
227
+ "fundRiskVolatility": {
228
+ "endDate": "2023-11-30T06:00:00.000",
229
+ "for1Year": {
230
+ "beta": 1.137,
231
+ "alpha": 5.675,
232
+ "rSquared": 98.8,
233
+ "sharpeRatio": 0.735,
234
+ "standardDeviation": 11.355,
235
+ },
236
+ "for3Year": {
237
+ "beta": None,
238
+ "alpha": None,
239
+ "rSquared": None,
240
+ "sharpeRatio": None,
241
+ "standardDeviation": None,
242
+ },
243
+ "for5Year": {
244
+ "beta": None,
245
+ "alpha": None,
246
+ "rSquared": None,
247
+ "sharpeRatio": None,
248
+ "standardDeviation": None,
249
+ },
250
+ "for10Year": {
251
+ "beta": None,
252
+ "alpha": None,
253
+ "rSquared": None,
254
+ "sharpeRatio": None,
255
+ "standardDeviation": None,
256
+ },
257
+ "for15Year": {
258
+ "beta": None,
259
+ "alpha": None,
260
+ "rSquared": None,
261
+ "sharpeRatio": None,
262
+ "standardDeviation": None,
263
+ },
264
+ "bestFitIndexName": None,
265
+ "forLongestTenure": None,
266
+ "bestFitBetaFor3Year": None,
267
+ "primaryIndexNameNew": "S&P BSE 100 India TR INR",
268
+ "bestFitAlphaFor3Year": None,
269
+ "bestFitRSquaredFor3Year": None,
270
+ },
271
+ },
272
+ },
273
+ 5: {
274
+ "quotes": {"expenseRatio": 0.0216, "lastTurnoverRatio": 0.2025},
275
+ "holdings": {
276
+ "equityHoldingPage": {
277
+ "pageSize": 100,
278
+ "totalPage": 1,
279
+ "pageNumber": 1,
280
+ "holdingList": [
281
+ {"isin": "INE002A01018", "weighting": 13.2},
282
+ {"isin": "INE090A01021", "weighting": 7.4},
283
+ {"isin": "INE040A01034", "weighting": 3.4},
284
+ ],
285
+ },
286
+ },
287
+ "list_info": {"isin": "MF123"},
288
+ "risk_measures": {
289
+ "cur": "INR",
290
+ "fundName": "Testing fund 2",
291
+ "indexName": "Morningstar India GR INR",
292
+ "categoryName": "Large-Cap",
293
+ "fundRiskVolatility": {
294
+ "endDate": "2023-11-30T06:00:00.000",
295
+ "for1Year": {
296
+ "beta": 1.041,
297
+ "alpha": 0.521,
298
+ "rSquared": 98.8,
299
+ "sharpeRatio": 0.354,
300
+ "standardDeviation": 11.355,
301
+ },
302
+ "for3Year": {
303
+ "beta": None,
304
+ "alpha": None,
305
+ "rSquared": None,
306
+ "sharpeRatio": None,
307
+ "standardDeviation": None,
308
+ },
309
+ "for5Year": {
310
+ "beta": None,
311
+ "alpha": None,
312
+ "rSquared": None,
313
+ "sharpeRatio": None,
314
+ "standardDeviation": None,
315
+ },
316
+ "for10Year": {
317
+ "beta": None,
318
+ "alpha": None,
319
+ "rSquared": None,
320
+ "sharpeRatio": None,
321
+ "standardDeviation": None,
322
+ },
323
+ "for15Year": {
324
+ "beta": None,
325
+ "alpha": None,
326
+ "rSquared": None,
327
+ "sharpeRatio": None,
328
+ "standardDeviation": None,
329
+ },
330
+ "bestFitIndexName": None,
331
+ "forLongestTenure": None,
332
+ "bestFitBetaFor3Year": None,
333
+ "primaryIndexNameNew": "S&P BSE 100 India TR INR",
334
+ "bestFitAlphaFor3Year": None,
335
+ "bestFitRSquaredFor3Year": None,
336
+ },
337
+ },
338
+ },
339
+ }
core/tests/test_scores.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.test import TestCase
2
+ from core.models import MutualFund, Stock
3
+ from core.mfrating.score_calculator import MutualFundScorer, MFRating
4
+ from core.tests.data import test_data
5
+
6
+
7
+ class MutualFundScorerTestCase(TestCase):
8
+ """
9
+ Test case for the MutualFundScorer class to test scores.
10
+ """
11
+
12
+ def setUp(self):
13
+ self.stock_data = [
14
+ {"isin_number": "INE040A01034", "rank": 10},
15
+ {"isin_number": "INE090A01021", "rank": 21},
16
+ {"isin_number": "INE002A01018", "rank": 131},
17
+ {"isin_number": "INE154A01025", "rank": 99},
18
+ {"isin_number": "INE018A01030", "rank": 31},
19
+ {"isin_number": "INE280A01028", "rank": 2},
20
+ ]
21
+
22
+ self.mutual_fund_data = [
23
+ {
24
+ "isin_number": "ISIN1",
25
+ "fund_name": "Testing Fund 1",
26
+ "rank": 1,
27
+ "aum": 837.3,
28
+ "crisil_rank": 4,
29
+ "security_id": "SEC1",
30
+ "data": test_data[1],
31
+ },
32
+ {
33
+ "isin_number": "ISIN2",
34
+ "fund_name": "Testing Fund 2",
35
+ "rank": 2,
36
+ "aum": 210.3,
37
+ "crisil_rank": 1,
38
+ "security_id": "SEC2",
39
+ "data": test_data[2],
40
+ },
41
+ {
42
+ "isin_number": "ISIN3",
43
+ "fund_name": "Testing Fund 3",
44
+ "rank": 3,
45
+ "aum": 639.3,
46
+ "crisil_rank": 3,
47
+ "security_id": "SEC3",
48
+ "data": test_data[3],
49
+ },
50
+ {
51
+ "isin_number": "ISIN4",
52
+ "fund_name": "Testing Fund 4",
53
+ "rank": 4,
54
+ "aum": 410.3,
55
+ "crisil_rank": 2,
56
+ "security_id": "SEC4",
57
+ "data": test_data[4],
58
+ },
59
+ {
60
+ "isin_number": "ISIN5",
61
+ "fund_name": "Testing Fund 5",
62
+ "rank": 5,
63
+ "aum": 1881.3,
64
+ "crisil_rank": 5,
65
+ "security_id": "SEC5",
66
+ "data": test_data[5],
67
+ },
68
+ ]
69
+
70
+ self.create_stock_objects()
71
+ self.create_mutual_fund_objects()
72
+ self.mf_scorer = MutualFundScorer()
73
+
74
+ def create_stock_objects(self):
75
+ """
76
+ Create stock objects using the predefined stock data.
77
+ """
78
+ self.stock_objects = [Stock.objects.create(**data) for data in self.stock_data]
79
+
80
+ def create_mutual_fund_objects(self):
81
+ """
82
+ Create mutual fund objects using the predefined mutual fund data.
83
+ """
84
+ self.mutual_fund_objects = [
85
+ MutualFund.objects.create(**data) for data in self.mutual_fund_data
86
+ ]
87
+
88
+ def test_get_scores_returns_sorted_list(self):
89
+ """
90
+ Test whether the get_scores method returns a sorted list of scores.
91
+ """
92
+ scores = self.mf_scorer.get_scores()
93
+ self.assertEqual(len(scores), 5)
94
+ self.assertEqual(
95
+ scores, sorted(scores, key=lambda x: x["overall_score"], reverse=True)
96
+ )
97
+ expected_scores = [0.4263, 0.3348, 0.2962, 0.2447, 0.2101]
98
+ for i, expected_score in enumerate(expected_scores):
99
+ self.assertAlmostEqual(
100
+ scores[i]["overall_score"], expected_score, delta=1e-4
101
+ )
core/text2sql/__init__.py ADDED
File without changes
core/text2sql/eval_queries.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ queries = [
2
+ {"Query Number": 1, "Complexity Level": "Simple", "Query Description": "Retrieve all mutual funds and their names",
3
+ "SQL Statement": "SELECT id, fund_name FROM core_mutualfund;"},
4
+ {"Query Number": 2, "Complexity Level": "Simple", "Query Description": "Get the total number of mutual funds",
5
+ "SQL Statement": "SELECT COUNT(*) FROM core_mutualfund;"},
6
+ {"Query Number": 3, "Complexity Level": "Simple", "Query Description": "List all unique ISIN numbers in the mutual fund holdings",
7
+ "SQL Statement": "SELECT DISTINCT isin_number FROM core_mfholdings;"},
8
+ {"Query Number": 4, "Complexity Level": "Simple", "Query Description":
9
+ "Find the mutual fund with the highest AUM (Assets Under Management)", "SQL Statement": "SELECT * FROM core_mutualfund ORDER BY aum DESC LIMIT 1;"},
10
+ {"Query Number": 5, "Complexity Level": "Simple", "Query Description": "Retrieve the top 5 mutual funds with the highest one-year return",
11
+ "SQL Statement": "SELECT * FROM core_mutualfund ORDER BY return_m12 DESC LIMIT 5;"},
12
+ {"Query Number": 6, "Complexity Level": "Medium", "Query Description": "List mutual funds with their holdings and respective sector codes",
13
+ "SQL Statement": "SELECT m.fund_name, h.sector, h.sector_code FROM core_mutualfund m JOIN core_mfholdings h ON m.id = h.mutual_fund_id;"},
14
+ {"Query Number": 7, "Complexity Level": "Medium", "Query Description": "Find the average expense ratio for all mutual funds",
15
+ "SQL Statement": "SELECT AVG(expense_ratio) FROM core_mutualfund;"},
16
+ {"Query Number": 8, "Complexity Level": "Medium", "Query Description": "Retrieve mutual funds with a specific country in their holdings",
17
+ "SQL Statement": "SELECT m.fund_name, h.country FROM core_mutualfund m JOIN core_mfholdings h ON m.id = h.mutual_fund_id WHERE h.country = 'USA';"},
18
+ {"Query Number": 9, "Complexity Level": "Medium", "Query Description":
19
+ "List mutual funds with volatility metrics (alpha, beta, sharpe_ratio)", "SQL Statement": "SELECT m.fund_name, v.alpha, v.beta, v.sharpe_ratio FROM core_mutualfund m JOIN core_mfvolatility v ON m.id = v.mutual_fund_id;"},
20
+ {"Query Number": 10, "Complexity Level": "Medium", "Query Description":
21
+ "Retrieve mutual funds with a NAV (Net Asset Value) greater than a specific value", "SQL Statement": "SELECT * FROM core_mutualfund WHERE nav > 100;"},
22
+ {"Query Number": 11, "Complexity Level": "High", "Query Description": "Find the mutual fund with the highest total market value of holdings",
23
+ "SQL Statement": "SELECT m.fund_name, MAX(h.market_value) AS max_market_value FROM core_mutualfund m JOIN core_mfholdings h ON m.id = h.mutual_fund_id;"},
24
+ {"Query Number": 12, "Complexity Level": "High", "Query Description": "List mutual funds with their average one-year return grouped by sector",
25
+ "SQL Statement": "SELECT h.sector, AVG(m.return_m12) AS avg_one_year_return FROM core_mutualfund m JOIN core_mfholdings h ON m.id = h.mutual_fund_id GROUP BY h.sector;"},
26
+ {"Query Number": 13, "Complexity Level": "High", "Query Description": "Retrieve mutual funds with a specific stock rating in their holdings",
27
+ "SQL Statement": "SELECT m.fund_name, h.stock_rating FROM core_mutualfund m JOIN core_mfholdings h ON m.id = h.mutual_fund_id WHERE h.stock_rating = 'A';"},
28
+ {"Query Number": 14, "Complexity Level": "High", "Query Description": "Find the mutual fund with the lowest standard deviation of volatility",
29
+ "SQL Statement": "SELECT m.fund_name, MIN(v.standard_deviation) AS min_standard_deviation FROM core_mutualfund m JOIN core_mfvolatility v ON m.id = v.mutual_fund_id;"},
30
+ {"Query Number": 15, "Complexity Level": "High", "Query Description": "List mutual funds with the highest number of shares in their holdings",
31
+ "SQL Statement": "SELECT m.fund_name, MAX(h.number_of_shares) AS max_number_of_shares FROM core_mutualfund m JOIN core_mfholdings h ON m.id = h.mutual_fund_id;"},
32
+ {"Query Number": 16, "Complexity Level": "More Complex", "Query Description": "Retrieve mutual funds and their holdings with a specific currency",
33
+ "SQL Statement": "SELECT m.fund_name, h.currency FROM core_mutualfund m JOIN core_mfholdings h ON m.id = h.mutual_fund_id WHERE h.currency = 'USD';"},
34
+ {"Query Number": 17, "Complexity Level": "More Complex", "Query Description": "Find the mutual fund with the highest total market value across all holdings",
35
+ "SQL Statement": "SELECT m.fund_name, SUM(h.market_value) AS total_market_value FROM core_mutualfund m JOIN core_mfholdings h ON m.id = h.mutual_fund_id GROUP BY m.fund_name ORDER BY total_market_value DESC LIMIT 1;"},
36
+ {"Query Number": 18, "Complexity Level": "More Complex", "Query Description": "List mutual funds with their average alpha and beta values for a specific year",
37
+ "SQL Statement": "SELECT m.fund_name, AVG(v.alpha) AS avg_alpha, AVG(v.beta) AS avg_beta FROM core_mutualfund m JOIN core_mfvolatility v ON m.id = v.mutual_fund_id WHERE v.year = '2023' GROUP BY m.fund_name;"},
38
+ {"Query Number": 19, "Complexity Level": "More Complex", "Query Description": "Retrieve mutual funds with a specific holding type and its market value",
39
+ "SQL Statement": "SELECT m.fund_name, h.holding_type, h.market_value FROM core_mutualfund m JOIN core_mfholdings h ON m.id = h.mutual_fund_id WHERE h.holding_type = 'Equity';"},
40
+ {"Query Number": 20, "Complexity Level": "More Complex", "Query Description": "List mutual funds with their rankings and corresponding CRISIL rankings",
41
+ "SQL Statement": "SELECT m.fund_name, m.rank, m.crisil_rank FROM core_mutualfund m;"},
42
+ {"Query Number": 21, "Complexity Level": "More Complex", "Query Description": "Find the mutual fund with the highest average one-year return across all years",
43
+ "SQL Statement": "SELECT m.fund_name, AVG(m.return_m12) AS avg_one_year_return FROM core_mutualfund m GROUP BY m.fund_name ORDER BY avg_one_year_return DESC LIMIT 1;"},
44
+ {"Query Number": 22, "Complexity Level": "More Complex", "Query Description": "Retrieve mutual funds with their top 3 holdings based on market value",
45
+ "SQL Statement": "SELECT m.fund_name, h.holding_name, h.market_value FROM core_mutualfund m JOIN core_mfholdings h ON m.id = h.mutual_fund_id ORDER BY h.market_value DESC LIMIT 3;"},
46
+ {"Query Number": 23, "Complexity Level": "More Complex", "Query Description": "List mutual funds with their volatility metrics for a specific year",
47
+ "SQL Statement": "SELECT m.fund_name, v.year, v.alpha, v.beta, v.sharpe_ratio, v.standard_deviation FROM core_mutualfund m JOIN core_mfvolatility v ON m.id = v.mutual_fund_id WHERE v.year = '2022';"},
48
+ {"Query Number": 24, "Complexity Level": "More Complex", "Query Description": "Find the mutual fund with the highest average market value per holding",
49
+ "SQL Statement": "SELECT m.fund_name, AVG(h.market_value) AS avg_market_value_per_holding FROM core_mutualfund m JOIN core_mfholdings h ON m.id = h.mutual_fund_id GROUP BY m.fund_name ORDER BY avg_market_value_per_holding DESC LIMIT 1;"},
50
+ {
51
+ "Query Number": 25,
52
+ "Complexity Level": "More Complex",
53
+ "Query Description": "Retrieve mutual funds with their total assets and the corresponding volatility metrics",
54
+ "SQL Statement": "SELECT m.fund_name, h.total_assets, v.alpha, v.beta, v.sharpe_ratio, v.standard_deviation FROM core_mutualfund m JOIN core_mfholdings h ON m.id = h.mutual_fund_id JOIN core_mfvolatility v ON m.id = v.mutual_fund_id;"
55
+ },
56
+ {
57
+ "Query Number": 26,
58
+ "Complexity Level": "More Complex",
59
+ "Query Description":"Retrieve mutual funds that have holdings in both technology and healthcare sectors, and provide a breakdown of their allocation percentages in each sector.",
60
+ "SQL Statement":"-"
61
+ },
62
+ {
63
+ "Query Number": 27,
64
+ "Complexity Level": "More Complex",
65
+ "Query Description": "Retrieve Mutual Funds with Highest Average Return and Lowest Expense Ratio",
66
+ "SQL Statement":"-"
67
+ },
68
+ {
69
+ "Query Number": 28,
70
+ "Complexity Level": "More Complex",
71
+ "Query Description":"Find Mutual Funds with Diversified Holdings",
72
+ "SQL Statement":"-"
73
+ },
74
+ {
75
+ "Query Number": 29,
76
+ "Complexity Level": "More Complex",
77
+ "Query Description": "Identify Mutual Funds with Consistent Performance and High AUM",
78
+ "SQL Statement":"-"
79
+ },
80
+ {
81
+ "Query Number": 30,
82
+ "Complexity Level": "More Complex",
83
+ "Query Description": "Calculate Weighted Average Return for Mutual Funds in a Specific Sector",
84
+ "SQL Statement":"-"
85
+ }
86
+ ]
core/text2sql/handler.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from core.models import MutualFund
3
+ from core.text2sql.ml_models import Text2SQLModel
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ class QueryDataHandler:
9
+ """
10
+ A class for handling queries and fetching data using a Text2SQL model and Django models.
11
+ """
12
+
13
+ def __init__(self):
14
+ self.mutual_fund = MutualFund()
15
+ self.text2sql_model = Text2SQLModel()
16
+
17
+ def get_data_from_query(self, prompt):
18
+ """
19
+ Generates a PostgreSQL query using the Text2SQL model based on the input prompt
20
+ and retrieves data using Django models.
21
+ """
22
+ # Use Text2SQL ML model to generate a PostgreSQL query
23
+ sql_query = self.text2sql_model.generate_query(prompt)
24
+ logger.info(f"SQL Query: {sql_query}")
25
+ # Use Django models to fetch data
26
+ return sql_query, self.mutual_fund.execute_query(sql_query)
core/text2sql/ml_models.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import replicate
2
+
3
+
4
+ class Text2SQLModel:
5
+ """
6
+ A class representing a Text-to-SQL model for generating SQL queries from LLM.
7
+ """
8
+
9
+ def __init__(self):
10
+ pass
11
+
12
+ def load_model(self):
13
+ """Loads the machine learning model for Text-to-SQL processing."""
14
+ pass
15
+
16
+ def generate_query(self, prompt):
17
+ output = replicate.run(
18
+ "ns-dev-sentience/sqlcoder:18bcabd866a64547daf3c6044cdebbd47a1f489571110087b80722848eb09398",
19
+ input={"prompt": prompt},
20
+ )
21
+ return (
22
+ output.split("```PostgresSQL")[-1].split("```")[0].split(";")[0].strip()
23
+ + ";"
24
+ )
core/text2sql/prompt.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def get_prompt(question):
2
+ database_schema = """
3
+ CREATE TABLE core_mutualfund (
4
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
5
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
6
+ updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
7
+ fund_name VARCHAR(200) UNIQUE,
8
+ isin_number VARCHAR(50) UNIQUE,
9
+ security_id VARCHAR(50) UNIQUE,
10
+ data JSONB,
11
+ rank INTEGER UNIQUE,
12
+ crisil_rank INTEGER,
13
+ aum DOUBLE PRECISION,
14
+ expense_ratio DOUBLE PRECISION,
15
+ nav DOUBLE PRECISION,
16
+ return_m12 DOUBLE PRECISION
17
+ );
18
+
19
+ CREATE TABLE core_mfholdings (
20
+ "id" uuid NOT NULL PRIMARY KEY,
21
+ "isin_number" varchar(20) NULL,
22
+ "security_id" varchar(20) NULL,
23
+ "sector" varchar(50) NULL,
24
+ "country" varchar(50) NULL,
25
+ "currency" varchar(100) NULL,
26
+ "weighting" double precision NULL,
27
+ "sector_code" varchar(100) NULL,
28
+ "holding_type" varchar(100) NULL,
29
+ "market_value" double precision NULL,
30
+ "stock_rating" varchar(100) NULL,
31
+ "total_assets" double precision NULL,
32
+ "currency_name" varchar(150) NULL,
33
+ "holding_name" varchar(100) NULL,
34
+ "holding_type" varchar(100) NULL,
35
+ "holding_type_id" varchar(100) NULL,
36
+ "number_of_shares" double precision NULL,
37
+ "one_year_return" double precision NULL,
38
+ "mutual_fund_id" uuid NOT NULL REFERENCES "core_mutualfund" ("id") DEFERRABLE INITIALLY DEFERRED
39
+ );
40
+
41
+ CREATE TABLE core_mfvolatility (
42
+ "id" uuid NOT NULL PRIMARY KEY,
43
+ "mutual_fund_id" uuid NOT NULL REFERENCES "core_mutualfund" ("id") DEFERRABLE INITIALLY DEFERRED,
44
+ "year" varchar(100) NOT NULL,
45
+ "alpha" double precision NULL,
46
+ "beta" double precision NULL,
47
+ "sharpe_ratio" double precision NULL,
48
+ "standard_deviation" double precision NULL
49
+ );
50
+
51
+ -- core_mfvolatility.mutual_fund_id can be joined with core_mutualfund.id
52
+ -- core_mfholdings.mutual_fund_id can be joined with core_mutualfund.id
53
+ """
54
+
55
+ sql_prompt = f"""
56
+ Your task is to convert a question into a PostgresSQL query, given a database schema.
57
+
58
+ ###Task:
59
+ Generate a SQL query that answers the question `{question}`.
60
+
61
+ ### Database Schema:
62
+ This query will run on a database whose schema is represented below:
63
+ {database_schema}
64
+
65
+ ### Response:
66
+ Based on your instructions, here is the PostgresSQL query I have generated to answer the question `{question}`:
67
+ ```PostgresSQL
68
+ """
69
+ return sql_prompt
core/urls.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from django.urls import path
2
+ from core.views import get_scores, get_mf_data
3
+
4
+ urlpatterns = [
5
+ path("mutual-fund-details/", get_scores, name="mutual-fund-details"),
6
+ path("get-mf-data/", get_mf_data, name="get-mf-data"),
7
+ ]
core/views.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+
3
+ """
4
+ import logging
5
+ from django.http import JsonResponse
6
+ from core.mfrating.score_calculator import MutualFundScorer
7
+ from core.text2sql.handler import QueryDataHandler
8
+ from core.text2sql.prompt import get_prompt
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def get_scores(request):
14
+ """
15
+ Retrieves scores for mutual funds based on various factors.
16
+ """
17
+ data = MutualFundScorer().get_scores()
18
+ return JsonResponse({"status": "success", "data": data}, status=200)
19
+
20
+
21
+ def get_mf_data(request):
22
+ """
23
+ Retrieves mutual fund data based on user query.
24
+ """
25
+ query = request.GET.get("query", "")
26
+ logger.info(f"Query: {query}")
27
+ prompt = get_prompt(query)
28
+ logger.info(f"Prompt: {prompt}")
29
+ query, data = QueryDataHandler().get_data_from_query(prompt)
30
+ return JsonResponse({"status": "success", "query": query, "data": data}, status=200)
data_pipeline/__init__.py ADDED
File without changes
data_pipeline/admin.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from django.contrib import admin
2
+
3
+ # Register your models here.
data_pipeline/apps.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class DataPipelineConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "data_pipeline"
data_pipeline/interfaces/__init__.py ADDED
File without changes
data_pipeline/interfaces/api_client.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Interface for API clients
3
+ """
4
+ from abc import ABC, abstractmethod
5
+
6
+
7
+ class DataClient(ABC):
8
+ """
9
+ Abstract class for API clients
10
+ """
11
+
12
+ @abstractmethod
13
+ def extract(self):
14
+ """
15
+ Extract data from API
16
+ """
17
+ if self.api_url is None:
18
+ raise Exception("No API URL provided")
19
+
20
+ def transform(self):
21
+ """
22
+ Placeholder method for future transformation logic.
23
+ """
24
+ self.transformed_data = self.api_response
25
+
26
+ @abstractmethod
27
+ def load(self):
28
+ """
29
+ Load data into target model
30
+ """
31
+ raise Exception("No model to transform")
32
+
33
+ def run(self) -> None:
34
+ self.extract()
35
+ self.transform()
36
+ self.load()
data_pipeline/interfaces/test_api_client.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+
3
+ """
4
+ from django.test import TestCase
5
+ from data_pipeline.interfaces.api_client import DataClient
6
+
7
+
8
+ class TestDataClient(TestCase):
9
+ def test_extract_raises_exception_on_instantiation(self):
10
+ with self.assertRaises(TypeError):
11
+ data_client = DataClient()
12
+
13
+ def test_extract_raises_exception_without_api_url(self):
14
+ class NewDataClientWithoutFunctionOverride(DataClient):
15
+ def __init__(self) -> None:
16
+ pass
17
+
18
+ with self.assertRaises(TypeError):
19
+ data_client = NewDataClientWithoutFunctionOverride()
20
+
21
+ def test_inherited_class(self):
22
+ class NewDataClientWith3FunctionOverride(DataClient):
23
+ def __init__(self) -> None:
24
+ pass
25
+
26
+ def extract(self):
27
+ pass
28
+
29
+ def transform(self):
30
+ pass
31
+
32
+ def load(self):
33
+ pass
34
+
35
+ data_client = NewDataClientWith3FunctionOverride()