prasanth.thangavel
commited on
Commit
·
afd8f1d
1
Parent(s):
b382e22
Made improvements
Browse files- app.py +14 -0
- requirements.txt +3 -1
- utils/hdb_utils.py +137 -0
app.py
CHANGED
@@ -9,6 +9,8 @@ import numpy as np
|
|
9 |
from utils.yfinance_utils import fetch_yfinance_daily
|
10 |
from utils.currency_utils import get_usd_sgd_rate
|
11 |
from utils.fd_utils import calculate_fd_returns
|
|
|
|
|
12 |
|
13 |
print("Starting the app ...")
|
14 |
|
@@ -46,6 +48,7 @@ selected_assets = st.sidebar.multiselect(
|
|
46 |
"Select Assets to Compare",
|
47 |
[
|
48 |
"Fixed Deposit",
|
|
|
49 |
"Gold",
|
50 |
"SGS Bonds",
|
51 |
"US Treasury Bonds",
|
@@ -69,6 +72,7 @@ selected_assets = st.sidebar.multiselect(
|
|
69 |
],
|
70 |
default=[
|
71 |
"Fixed Deposit",
|
|
|
72 |
"Gold",
|
73 |
"US Treasury Bonds", "SGS Bonds",
|
74 |
"S&P 500", "Dow Jones", "NASDAQ Composite", #"NASDAQ Large Cap", "NASDAQ 100",
|
@@ -86,6 +90,7 @@ currency_symbol = "$" if currency == "USD" else "S$"
|
|
86 |
# Create a dictionary of tickers for yfinance
|
87 |
tickers = {
|
88 |
"Gold": "GC=F",
|
|
|
89 |
"SGS Bonds": "A35.SI", # Nikko AM SGD Investment Grade Corporate Bond ETF
|
90 |
"US Treasury Bonds": "TLT", # iShares 20+ Year Treasury Bond ETF
|
91 |
"NASDAQ Composite": "^IXIC",
|
@@ -138,6 +143,15 @@ for asset in selected_assets:
|
|
138 |
fd_values = fd_values * usd_to_sgd
|
139 |
asset_series[asset] = pd.Series(fd_values, index=fd_index)
|
140 |
actual_start_dates[asset] = asset_start
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
141 |
else:
|
142 |
price_data = fetch_yfinance_daily(tickers[asset], asset_start, asset_end)
|
143 |
if price_data is not None and not price_data.empty:
|
|
|
9 |
from utils.yfinance_utils import fetch_yfinance_daily
|
10 |
from utils.currency_utils import get_usd_sgd_rate
|
11 |
from utils.fd_utils import calculate_fd_returns
|
12 |
+
from utils.hdb_utils import calculate_hdb_returns
|
13 |
+
|
14 |
|
15 |
print("Starting the app ...")
|
16 |
|
|
|
48 |
"Select Assets to Compare",
|
49 |
[
|
50 |
"Fixed Deposit",
|
51 |
+
"HDB",
|
52 |
"Gold",
|
53 |
"SGS Bonds",
|
54 |
"US Treasury Bonds",
|
|
|
72 |
],
|
73 |
default=[
|
74 |
"Fixed Deposit",
|
75 |
+
"HDB",
|
76 |
"Gold",
|
77 |
"US Treasury Bonds", "SGS Bonds",
|
78 |
"S&P 500", "Dow Jones", "NASDAQ Composite", #"NASDAQ Large Cap", "NASDAQ 100",
|
|
|
90 |
# Create a dictionary of tickers for yfinance
|
91 |
tickers = {
|
92 |
"Gold": "GC=F",
|
93 |
+
"HDB": "A12.SI",
|
94 |
"SGS Bonds": "A35.SI", # Nikko AM SGD Investment Grade Corporate Bond ETF
|
95 |
"US Treasury Bonds": "TLT", # iShares 20+ Year Treasury Bond ETF
|
96 |
"NASDAQ Composite": "^IXIC",
|
|
|
143 |
fd_values = fd_values * usd_to_sgd
|
144 |
asset_series[asset] = pd.Series(fd_values, index=fd_index)
|
145 |
actual_start_dates[asset] = asset_start
|
146 |
+
elif asset == "HDB":
|
147 |
+
hdb_values = calculate_hdb_returns(asset_start, asset_end, initial_investment)
|
148 |
+
if hdb_values is not None:
|
149 |
+
if currency == "SGD":
|
150 |
+
hdb_values = hdb_values * usd_to_sgd
|
151 |
+
asset_series[asset] = hdb_values
|
152 |
+
actual_start_dates[asset] = asset_start
|
153 |
+
else:
|
154 |
+
failed_assets.append(asset)
|
155 |
else:
|
156 |
price_data = fetch_yfinance_daily(tickers[asset], asset_start, asset_end)
|
157 |
if price_data is not None and not price_data.empty:
|
requirements.txt
CHANGED
@@ -3,4 +3,6 @@ pandas==2.2.1
|
|
3 |
yfinance==0.2.56
|
4 |
plotly==5.19.0
|
5 |
python-dotenv==1.0.1
|
6 |
-
numpy==1.26.4
|
|
|
|
|
|
3 |
yfinance==0.2.56
|
4 |
plotly==5.19.0
|
5 |
python-dotenv==1.0.1
|
6 |
+
numpy==1.26.4
|
7 |
+
requests==2.31.0
|
8 |
+
beautifulsoup4==4.12.3
|
utils/hdb_utils.py
ADDED
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import numpy as np
|
3 |
+
from datetime import datetime
|
4 |
+
import requests
|
5 |
+
import os
|
6 |
+
from functools import lru_cache
|
7 |
+
from dotenv import load_dotenv
|
8 |
+
|
9 |
+
# Load environment variables
|
10 |
+
load_dotenv()
|
11 |
+
|
12 |
+
# SingStat API configuration
|
13 |
+
SINGSTAT_API_KEY = os.getenv('SINGSTAT_API_KEY')
|
14 |
+
SINGSTAT_API_URL = "https://tablebuilder.singstat.gov.sg/api/table/tabledata"
|
15 |
+
|
16 |
+
# HDB Resale Price Index table ID
|
17 |
+
HDB_TABLE_ID = "M212881"
|
18 |
+
|
19 |
+
@lru_cache(maxsize=1) # Cache the HDB data for 1 day
|
20 |
+
def fetch_hdb_price_index():
|
21 |
+
"""
|
22 |
+
Fetch HDB Resale Price Index from SingStat TableBuilder API
|
23 |
+
"""
|
24 |
+
try:
|
25 |
+
if not SINGSTAT_API_KEY:
|
26 |
+
raise Exception("SINGSTAT_API_KEY not found in environment variables")
|
27 |
+
|
28 |
+
# API parameters
|
29 |
+
params = {
|
30 |
+
"key": SINGSTAT_API_KEY,
|
31 |
+
"resourceId": HDB_TABLE_ID,
|
32 |
+
"variable": "HDB Resale Price Index",
|
33 |
+
"timeFrom": "2000",
|
34 |
+
"timeTo": datetime.now().strftime("%Y")
|
35 |
+
}
|
36 |
+
|
37 |
+
# Make API request
|
38 |
+
response = requests.get(SINGSTAT_API_URL, params=params)
|
39 |
+
response.raise_for_status()
|
40 |
+
|
41 |
+
# Parse response
|
42 |
+
data = response.json()
|
43 |
+
if 'Data' not in data:
|
44 |
+
raise Exception("Invalid response format from SingStat API")
|
45 |
+
|
46 |
+
# Extract the data
|
47 |
+
records = data['Data']
|
48 |
+
if not records:
|
49 |
+
raise Exception("No records found in SingStat API response")
|
50 |
+
|
51 |
+
# Convert to DataFrame
|
52 |
+
df = pd.DataFrame(records)
|
53 |
+
|
54 |
+
# Process the data
|
55 |
+
# The exact column names might need adjustment based on the actual API response
|
56 |
+
df['year'] = pd.to_datetime(df['year'], format='%Y')
|
57 |
+
df['value'] = pd.to_numeric(df['value'])
|
58 |
+
|
59 |
+
# Create a dictionary of year to index
|
60 |
+
yearly_data = df.set_index('year')['value']
|
61 |
+
|
62 |
+
# Convert to dictionary with string keys
|
63 |
+
result = {str(year.year): float(value) for year, value in yearly_data.items()}
|
64 |
+
return result
|
65 |
+
|
66 |
+
except Exception as e:
|
67 |
+
print(f"Error fetching HDB data from SingStat: {e}")
|
68 |
+
# Fallback to hardcoded data if API fails
|
69 |
+
return {
|
70 |
+
'2000': 78.3,
|
71 |
+
'2001': 75.6,
|
72 |
+
'2002': 74.1,
|
73 |
+
'2003': 73.1,
|
74 |
+
'2004': 74.3,
|
75 |
+
'2005': 80.0,
|
76 |
+
'2006': 88.0,
|
77 |
+
'2007': 100.0,
|
78 |
+
'2008': 110.0,
|
79 |
+
'2009': 100.0, # Base year
|
80 |
+
'2010': 105.0,
|
81 |
+
'2011': 111.0,
|
82 |
+
'2012': 118.0,
|
83 |
+
'2013': 123.0,
|
84 |
+
'2014': 120.0,
|
85 |
+
'2015': 115.0,
|
86 |
+
'2016': 110.0,
|
87 |
+
'2017': 108.0,
|
88 |
+
'2018': 110.0,
|
89 |
+
'2019': 111.0,
|
90 |
+
'2020': 112.0,
|
91 |
+
'2021': 120.0,
|
92 |
+
'2022': 130.0,
|
93 |
+
'2023': 140.0,
|
94 |
+
'2024': 145.0
|
95 |
+
}
|
96 |
+
|
97 |
+
def calculate_hdb_returns(start_date, end_date, initial_investment):
|
98 |
+
"""
|
99 |
+
Calculate HDB price returns based on the HDB Resale Price Index
|
100 |
+
"""
|
101 |
+
try:
|
102 |
+
# Get the latest HDB price index data
|
103 |
+
hdb_index = fetch_hdb_price_index()
|
104 |
+
|
105 |
+
# Create a date range for the investment period
|
106 |
+
date_range = pd.date_range(start=start_date, end=end_date)
|
107 |
+
|
108 |
+
# Convert the yearly data to a Series with datetime index
|
109 |
+
yearly_dates = pd.to_datetime([f"{year}-01-01" for year in hdb_index.keys()])
|
110 |
+
yearly_values = list(hdb_index.values())
|
111 |
+
yearly_prices = pd.Series(yearly_values, index=yearly_dates)
|
112 |
+
|
113 |
+
# Sort by date to ensure proper interpolation
|
114 |
+
yearly_prices = yearly_prices.sort_index()
|
115 |
+
|
116 |
+
# Interpolate the price index for each day
|
117 |
+
daily_prices = yearly_prices.reindex(date_range, method='ffill')
|
118 |
+
|
119 |
+
# Calculate the return based on the price index
|
120 |
+
if not daily_prices.empty:
|
121 |
+
start_price = daily_prices.iloc[0]
|
122 |
+
daily_returns = daily_prices / start_price
|
123 |
+
investment_value = initial_investment * daily_returns
|
124 |
+
return investment_value
|
125 |
+
|
126 |
+
return None
|
127 |
+
|
128 |
+
except Exception as e:
|
129 |
+
print(f"Error calculating HDB returns: {e}")
|
130 |
+
return None
|
131 |
+
|
132 |
+
if __name__ == "__main__":
|
133 |
+
# Test the function
|
134 |
+
start = datetime(2000, 1, 1)
|
135 |
+
end = datetime(2024, 1, 1)
|
136 |
+
result = calculate_hdb_returns(start, end, 100000)
|
137 |
+
print(result)
|