prasanth.thangavel commited on
Commit
2aaf2a2
·
1 Parent(s): ea5b3dc

First commit of the app

Browse files
.gitignore ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual Environment
24
+ venv/
25
+ env/
26
+ ENV/
27
+ .env
28
+ .venv
29
+ env.bak/
30
+ venv.bak/
31
+
32
+ # IDE specific files
33
+ .idea/
34
+ .vscode/
35
+ *.swp
36
+ *.swo
37
+ .DS_Store
38
+
39
+ # Jupyter Notebook
40
+ .ipynb_checkpoints
41
+
42
+ # Distribution / packaging
43
+ .Python
44
+ env/
45
+ build/
46
+ develop-eggs/
47
+ dist/
48
+ downloads/
49
+ eggs/
50
+ .eggs/
51
+ lib/
52
+ lib64/
53
+ parts/
54
+ sdist/
55
+ var/
56
+ wheels/
57
+ *.egg-info/
58
+ .installed.cfg
59
+ *.egg
60
+
61
+ # Unit test / coverage reports
62
+ htmlcov/
63
+ .tox/
64
+ .coverage
65
+ .coverage.*
66
+ .cache
67
+ nosetests.xml
68
+ coverage.xml
69
+ *.cover
70
+ .hypothesis/
71
+
72
+ # Streamlit
73
+ .streamlit/
74
+
75
+ # Logs
76
+ *.log
77
+ logs/
78
+
79
+ # Local development settings
80
+ .env.local
81
+ .env.development.local
82
+ .env.test.local
83
+ .env.production.local
84
+
85
+ # Misc
86
+ .DS_Store
87
+ Thumbs.db
README.md CHANGED
@@ -12,3 +12,72 @@ short_description: An asset class comparator for my personal usecase
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
15
+
16
+ # Asset Class Performance Comparison
17
+
18
+ This Streamlit app allows you to compare the performance of different asset classes over time, including stocks, bonds, gold, and fixed deposits.
19
+
20
+ ## Features
21
+
22
+ - Compare multiple asset classes simultaneously
23
+ - Choose between USD and SGD currencies
24
+ - Customize investment parameters (initial amount, time period)
25
+ - View performance graphs and annualized returns
26
+ - Support for major indices and individual stocks
27
+
28
+ ## How to Use
29
+
30
+ 1. Select your preferred currency (USD or SGD)
31
+ 2. Enter your initial investment amount
32
+ 3. Choose the time period for comparison
33
+ 4. Select the assets you want to compare
34
+ 5. View the performance graph and returns
35
+
36
+ ## Assets Available
37
+
38
+ - Fixed Deposit
39
+ - Gold
40
+ - SGS Bonds
41
+ - US Treasury Bonds
42
+ - Major Indices (NASDAQ, S&P 500, Dow Jones)
43
+ - Individual Stocks (Microsoft, Google, Nvidia, Apple, etc.)
44
+
45
+ ## Technical Details
46
+
47
+ The app uses:
48
+ - Streamlit for the web interface
49
+ - yfinance for market data
50
+ - Plotly for interactive graphs
51
+ - Pandas for data manipulation
52
+
53
+ ## Hosting
54
+
55
+ This app is hosted on Hugging Face Spaces. You can access it at [link to be added after deployment].
56
+
57
+ ## Local Development
58
+
59
+ To run locally:
60
+ 1. Clone this repository
61
+ 2. Install dependencies: `pip install -r requirements.txt`
62
+ 3. Run the app: `streamlit run app.py`
63
+
64
+ ## Project Structure
65
+
66
+ ```
67
+ asset-class-comparison/
68
+ ├── app.py # Main Streamlit application
69
+ ├── requirements.txt # Python dependencies
70
+ ├── README.md # This file
71
+ └── utils/ # Utility functions
72
+ ├── yfinance_utils.py # yfinance data fetching utilities
73
+ ├── currency_utils.py # Currency conversion utilities
74
+ └── fd_utils.py # Fixed deposit calculation utilities
75
+ ```
76
+
77
+ ## Contributing
78
+
79
+ Contributions are welcome! Please feel free to submit a Pull Request.
80
+
81
+ ## License
82
+
83
+ This project is licensed under the MIT License - see the LICENSE file for details.
app.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import yfinance as yf
4
+ import plotly.graph_objects as go
5
+ from datetime import datetime, timedelta
6
+ import numpy as np
7
+
8
+ # Import utility functions
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
+ # Set page config
14
+ st.set_page_config(page_title="Asset Class Comparison", layout="wide")
15
+
16
+ # Title and description
17
+ st.title("Asset Class Performance Comparison")
18
+ st.write("Compare the performance of different asset classes over time")
19
+
20
+ # Sidebar for user inputs
21
+ st.sidebar.header("Investment Parameters")
22
+ currency = st.sidebar.selectbox("Display Currency", ["USD", "SGD"], index=0)
23
+ initial_investment = st.sidebar.number_input(f"Initial Investment Amount ({currency})", min_value=1000, value=10000, step=1000)
24
+ start_date = st.sidebar.date_input("Start Date", value=datetime.now() - timedelta(days=365*10))
25
+ user_end_date = st.sidebar.date_input("End Date", value=datetime.now())
26
+ fd_rate = st.sidebar.number_input("Fixed Deposit Rate (%)", min_value=0.0, value=2.9, step=0.1) / 100
27
+
28
+ # Asset selection
29
+ selected_assets = st.sidebar.multiselect(
30
+ "Select Assets to Compare",
31
+ [
32
+ "Fixed Deposit",
33
+ "Gold",
34
+ "SGS Bonds",
35
+ "US Treasury Bonds",
36
+ "NASDAQ Composite",
37
+ "NASDAQ Large Cap",
38
+ "NASDAQ 100",
39
+ "S&P 500",
40
+ "Dow Jones",
41
+ "Microsoft",
42
+ "Google",
43
+ "Nvidia",
44
+ "Apple",
45
+ "Amazon",
46
+ "Tesla",
47
+ "Netflix",
48
+ "Meta",
49
+ ],
50
+ default=["Fixed Deposit", "Microsoft", "Google", "Nvidia"]
51
+ )
52
+
53
+ # Today's date for reference
54
+ today = datetime.now().date()
55
+
56
+ usd_to_sgd = get_usd_sgd_rate() if currency == "SGD" else 1.0
57
+ currency_symbol = "$" if currency == "USD" else "S$"
58
+
59
+ # Create a dictionary of tickers for yfinance
60
+ tickers = {
61
+ "Gold": "GC=F",
62
+ "SGS Bonds": "^TNX",
63
+ "US Treasury Bonds": "^TNX",
64
+ "NASDAQ Composite": "^IXIC",
65
+ "NASDAQ Large Cap": "^NDX",
66
+ "NASDAQ 100": "^NDX",
67
+ "S&P 500": "^GSPC",
68
+ "Dow Jones": "^DJI",
69
+ "Microsoft": "MSFT",
70
+ "Google": "GOOGL",
71
+ "Nvidia": "NVDA",
72
+ "Apple": "AAPL",
73
+ "Amazon": "AMZN",
74
+ "Tesla": "TSLA",
75
+ "Netflix": "NFLX",
76
+ "Meta": "META",
77
+ }
78
+
79
+ # Determine the effective end date for each asset
80
+ asset_end_dates = {}
81
+ for asset in selected_assets:
82
+ if asset == "Fixed Deposit":
83
+ asset_end_dates[asset] = user_end_date
84
+ else:
85
+ if user_end_date > today:
86
+ asset_end_dates[asset] = today
87
+ else:
88
+ asset_end_dates[asset] = user_end_date
89
+
90
+ # Warn the user if a future end date is selected for market assets
91
+ if any(user_end_date > today and asset != "Fixed Deposit" for asset in selected_assets):
92
+ st.warning(f"Market data is only available up to today ({today}). For market assets, the end date has been set to today.")
93
+
94
+ # Calculate returns for each selected asset
95
+ asset_series = {}
96
+ failed_assets = []
97
+ actual_start_dates = {}
98
+
99
+ for asset in selected_assets:
100
+ asset_start = start_date
101
+ asset_end = asset_end_dates[asset]
102
+ if asset == "Fixed Deposit":
103
+ fd_index = pd.date_range(start=asset_start, end=user_end_date)
104
+ daily_rate = (1 + fd_rate) ** (1/365) - 1
105
+ fd_values = initial_investment * (1 + daily_rate) ** np.arange(len(fd_index))
106
+ if currency == "SGD":
107
+ fd_values = fd_values * usd_to_sgd
108
+ asset_series[asset] = pd.Series(fd_values, index=fd_index)
109
+ actual_start_dates[asset] = asset_start
110
+ else:
111
+ price_data = fetch_yfinance_daily(tickers[asset], asset_start, asset_end)
112
+ if price_data is not None and not price_data.empty:
113
+ price_data = price_data.sort_index()
114
+ actual_start = price_data.index[0]
115
+ actual_start_dates[asset] = actual_start
116
+ aligned_index = pd.date_range(start=actual_start, end=asset_end)
117
+ price_data = price_data.reindex(aligned_index)
118
+ price_data = price_data.ffill()
119
+ asset_values = initial_investment * (price_data / price_data.iloc[0])
120
+ if currency == "SGD":
121
+ asset_values = asset_values * usd_to_sgd
122
+ asset_series[asset] = asset_values
123
+ else:
124
+ failed_assets.append(asset)
125
+
126
+ # Combine all asset series into a single DataFrame
127
+ if asset_series:
128
+ returns_data = pd.DataFrame(asset_series)
129
+ else:
130
+ returns_data = pd.DataFrame()
131
+
132
+ # Remove failed assets from selected_assets (except FD)
133
+ selected_assets = [asset for asset in selected_assets if asset not in failed_assets or asset == "Fixed Deposit"]
134
+
135
+ if not selected_assets:
136
+ st.error("No assets could be loaded. Please try different assets.")
137
+ st.stop()
138
+
139
+ # Create the plot
140
+ fig = go.Figure()
141
+
142
+ for asset in selected_assets:
143
+ fig.add_trace(go.Scatter(
144
+ x=returns_data.index,
145
+ y=returns_data[asset],
146
+ name=asset,
147
+ mode='lines'
148
+ ))
149
+
150
+ fig.update_layout(
151
+ title="Asset Performance Comparison",
152
+ xaxis_title="Date",
153
+ yaxis_title=f"Investment Value ({currency_symbol})",
154
+ hovermode="x unified",
155
+ height=600
156
+ )
157
+
158
+ # Display the plot
159
+ st.plotly_chart(fig, use_container_width=True)
160
+
161
+ # Calculate and display final returns
162
+ st.subheader("Final Investment Values")
163
+ for asset in selected_assets:
164
+ valid_series = returns_data[asset].dropna()
165
+ if not valid_series.empty:
166
+ final_value = valid_series.iloc[-1]
167
+ st.write(f"{asset}: {currency_symbol}{final_value:,.2f}")
168
+ else:
169
+ st.write(f"{asset}: Data unavailable")
170
+
171
+ # Calculate and display annualized returns
172
+ st.subheader("Annualized Returns")
173
+ for asset in selected_assets:
174
+ valid_series = returns_data[asset].dropna()
175
+ if len(valid_series) > 1:
176
+ actual_start = actual_start_dates[asset]
177
+ days = (valid_series.index[-1] - valid_series.index[0]).days
178
+ years = days / 365
179
+ final_value = valid_series.iloc[-1]
180
+ annualized_return = ((final_value / initial_investment) ** (1/years) - 1) * 100
181
+ if pd.Timestamp(actual_start).date() > start_date:
182
+ st.write(f"{asset}: {annualized_return:.2f}% (Data available from {actual_start.strftime('%Y-%m-%d')})")
183
+ else:
184
+ st.write(f"{asset}: {annualized_return:.2f}%")
185
+ else:
186
+ st.write(f"{asset}: N/A")
187
+
188
+ # Show warnings for data availability
189
+ for asset in selected_assets:
190
+ if asset in actual_start_dates and pd.Timestamp(actual_start_dates[asset]).date() > start_date:
191
+ st.warning(f"Data for {asset} is only available from {actual_start_dates[asset].strftime('%Y-%m-%d')}. The analysis starts from this date.")
192
+
193
+ # Show warning for failed assets
194
+ if failed_assets:
195
+ st.warning(f"Could not load data for the following assets: {', '.join(failed_assets)}")
notebooks/scratchpad.ipynb ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 2,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "import yfinance as yf"
10
+ ]
11
+ },
12
+ {
13
+ "cell_type": "code",
14
+ "execution_count": 14,
15
+ "metadata": {},
16
+ "outputs": [
17
+ {
18
+ "name": "stderr",
19
+ "output_type": "stream",
20
+ "text": [
21
+ "[*********************100%***********************] 1 of 1 completed\n"
22
+ ]
23
+ }
24
+ ],
25
+ "source": [
26
+ "ticker = \"META\"\n",
27
+ "start_date = \"2010-01-01\"\n",
28
+ "end_date = \"2020-01-10\"\n",
29
+ "\n",
30
+ "data = yf.download(ticker, start=start_date, end=end_date) # [\"Close\"][ticker]"
31
+ ]
32
+ },
33
+ {
34
+ "cell_type": "code",
35
+ "execution_count": 15,
36
+ "metadata": {},
37
+ "outputs": [
38
+ {
39
+ "data": {
40
+ "text/html": [
41
+ "<div>\n",
42
+ "<style scoped>\n",
43
+ " .dataframe tbody tr th:only-of-type {\n",
44
+ " vertical-align: middle;\n",
45
+ " }\n",
46
+ "\n",
47
+ " .dataframe tbody tr th {\n",
48
+ " vertical-align: top;\n",
49
+ " }\n",
50
+ "\n",
51
+ " .dataframe thead tr th {\n",
52
+ " text-align: left;\n",
53
+ " }\n",
54
+ "\n",
55
+ " .dataframe thead tr:last-of-type th {\n",
56
+ " text-align: right;\n",
57
+ " }\n",
58
+ "</style>\n",
59
+ "<table border=\"1\" class=\"dataframe\">\n",
60
+ " <thead>\n",
61
+ " <tr>\n",
62
+ " <th>Price</th>\n",
63
+ " <th>Close</th>\n",
64
+ " <th>High</th>\n",
65
+ " <th>Low</th>\n",
66
+ " <th>Open</th>\n",
67
+ " <th>Volume</th>\n",
68
+ " </tr>\n",
69
+ " <tr>\n",
70
+ " <th>Ticker</th>\n",
71
+ " <th>META</th>\n",
72
+ " <th>META</th>\n",
73
+ " <th>META</th>\n",
74
+ " <th>META</th>\n",
75
+ " <th>META</th>\n",
76
+ " </tr>\n",
77
+ " <tr>\n",
78
+ " <th>Date</th>\n",
79
+ " <th></th>\n",
80
+ " <th></th>\n",
81
+ " <th></th>\n",
82
+ " <th></th>\n",
83
+ " <th></th>\n",
84
+ " </tr>\n",
85
+ " </thead>\n",
86
+ " <tbody>\n",
87
+ " <tr>\n",
88
+ " <th>2012-05-18</th>\n",
89
+ " <td>38.050671</td>\n",
90
+ " <td>44.788914</td>\n",
91
+ " <td>37.821750</td>\n",
92
+ " <td>41.852752</td>\n",
93
+ " <td>573576400</td>\n",
94
+ " </tr>\n",
95
+ " <tr>\n",
96
+ " <th>2012-05-21</th>\n",
97
+ " <td>33.870365</td>\n",
98
+ " <td>36.488029</td>\n",
99
+ " <td>32.845198</td>\n",
100
+ " <td>36.358638</td>\n",
101
+ " <td>168192700</td>\n",
102
+ " </tr>\n",
103
+ " <tr>\n",
104
+ " <th>2012-05-22</th>\n",
105
+ " <td>30.854584</td>\n",
106
+ " <td>33.432435</td>\n",
107
+ " <td>30.794866</td>\n",
108
+ " <td>32.457032</td>\n",
109
+ " <td>101786600</td>\n",
110
+ " </tr>\n",
111
+ " <tr>\n",
112
+ " <th>2012-05-23</th>\n",
113
+ " <td>31.849892</td>\n",
114
+ " <td>32.347546</td>\n",
115
+ " <td>31.212894</td>\n",
116
+ " <td>31.222848</td>\n",
117
+ " <td>73600000</td>\n",
118
+ " </tr>\n",
119
+ " <tr>\n",
120
+ " <th>2012-05-24</th>\n",
121
+ " <td>32.875057</td>\n",
122
+ " <td>33.054213</td>\n",
123
+ " <td>31.620969</td>\n",
124
+ " <td>32.795434</td>\n",
125
+ " <td>50237200</td>\n",
126
+ " </tr>\n",
127
+ " <tr>\n",
128
+ " <th>...</th>\n",
129
+ " <td>...</td>\n",
130
+ " <td>...</td>\n",
131
+ " <td>...</td>\n",
132
+ " <td>...</td>\n",
133
+ " <td>...</td>\n",
134
+ " </tr>\n",
135
+ " <tr>\n",
136
+ " <th>2020-01-03</th>\n",
137
+ " <td>207.691162</td>\n",
138
+ " <td>209.413043</td>\n",
139
+ " <td>205.979229</td>\n",
140
+ " <td>206.238019</td>\n",
141
+ " <td>11188400</td>\n",
142
+ " </tr>\n",
143
+ " <tr>\n",
144
+ " <th>2020-01-06</th>\n",
145
+ " <td>211.602707</td>\n",
146
+ " <td>211.781855</td>\n",
147
+ " <td>205.551226</td>\n",
148
+ " <td>205.730374</td>\n",
149
+ " <td>17058900</td>\n",
150
+ " </tr>\n",
151
+ " <tr>\n",
152
+ " <th>2020-01-07</th>\n",
153
+ " <td>212.060547</td>\n",
154
+ " <td>213.573421</td>\n",
155
+ " <td>210.756694</td>\n",
156
+ " <td>211.821682</td>\n",
157
+ " <td>14912400</td>\n",
158
+ " </tr>\n",
159
+ " <tr>\n",
160
+ " <th>2020-01-08</th>\n",
161
+ " <td>214.210419</td>\n",
162
+ " <td>215.225638</td>\n",
163
+ " <td>211.612661</td>\n",
164
+ " <td>212.000831</td>\n",
165
+ " <td>13475000</td>\n",
166
+ " </tr>\n",
167
+ " <tr>\n",
168
+ " <th>2020-01-09</th>\n",
169
+ " <td>217.275970</td>\n",
170
+ " <td>217.355597</td>\n",
171
+ " <td>215.265442</td>\n",
172
+ " <td>216.519526</td>\n",
173
+ " <td>12642800</td>\n",
174
+ " </tr>\n",
175
+ " </tbody>\n",
176
+ "</table>\n",
177
+ "<p>1923 rows × 5 columns</p>\n",
178
+ "</div>"
179
+ ],
180
+ "text/plain": [
181
+ "Price Close High Low Open Volume\n",
182
+ "Ticker META META META META META\n",
183
+ "Date \n",
184
+ "2012-05-18 38.050671 44.788914 37.821750 41.852752 573576400\n",
185
+ "2012-05-21 33.870365 36.488029 32.845198 36.358638 168192700\n",
186
+ "2012-05-22 30.854584 33.432435 30.794866 32.457032 101786600\n",
187
+ "2012-05-23 31.849892 32.347546 31.212894 31.222848 73600000\n",
188
+ "2012-05-24 32.875057 33.054213 31.620969 32.795434 50237200\n",
189
+ "... ... ... ... ... ...\n",
190
+ "2020-01-03 207.691162 209.413043 205.979229 206.238019 11188400\n",
191
+ "2020-01-06 211.602707 211.781855 205.551226 205.730374 17058900\n",
192
+ "2020-01-07 212.060547 213.573421 210.756694 211.821682 14912400\n",
193
+ "2020-01-08 214.210419 215.225638 211.612661 212.000831 13475000\n",
194
+ "2020-01-09 217.275970 217.355597 215.265442 216.519526 12642800\n",
195
+ "\n",
196
+ "[1923 rows x 5 columns]"
197
+ ]
198
+ },
199
+ "execution_count": 15,
200
+ "metadata": {},
201
+ "output_type": "execute_result"
202
+ }
203
+ ],
204
+ "source": [
205
+ "data"
206
+ ]
207
+ },
208
+ {
209
+ "cell_type": "code",
210
+ "execution_count": null,
211
+ "metadata": {},
212
+ "outputs": [],
213
+ "source": []
214
+ }
215
+ ],
216
+ "metadata": {
217
+ "kernelspec": {
218
+ "display_name": "return-on-investment",
219
+ "language": "python",
220
+ "name": "python3"
221
+ },
222
+ "language_info": {
223
+ "codemirror_mode": {
224
+ "name": "ipython",
225
+ "version": 3
226
+ },
227
+ "file_extension": ".py",
228
+ "mimetype": "text/x-python",
229
+ "name": "python",
230
+ "nbconvert_exporter": "python",
231
+ "pygments_lexer": "ipython3",
232
+ "version": "3.12.9"
233
+ }
234
+ },
235
+ "nbformat": 4,
236
+ "nbformat_minor": 2
237
+ }
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ streamlit==1.32.0
2
+ pandas==2.2.1
3
+ yfinance==0.2.36
4
+ plotly==5.19.0
5
+ python-dotenv==1.0.1
6
+ numpy==1.26.4
tests/integration/test_yfinance_utils_integration.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
4
+
5
+ from utils import yfinance_utils
6
+ import pandas as pd
7
+
8
+ def test_fetch_yfinance_daily_real():
9
+ result = yfinance_utils.fetch_yfinance_daily('MSFT', '2023-01-01', '2023-01-10')
10
+ assert result is not None
11
+ assert not result.empty
12
+ assert isinstance(result, pd.Series)
tests/unit/test_currency_utils.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
4
+
5
+ import pytest
6
+ from unittest.mock import patch
7
+ from utils import currency_utils
8
+
9
+ def test_get_usd_sgd_rate_success():
10
+ with patch('yfinance.download') as mock_download:
11
+ import pandas as pd
12
+ df = pd.DataFrame({'Close': [1.3, 1.35]}, index=pd.date_range('2020-01-01', periods=2))
13
+ mock_download.return_value = df
14
+ rate = currency_utils.get_usd_sgd_rate()
15
+ assert rate == 1.35
16
+
17
+ def test_get_usd_sgd_rate_fail():
18
+ with patch('yfinance.download') as mock_download:
19
+ mock_download.side_effect = Exception('fail')
20
+ rate = currency_utils.get_usd_sgd_rate()
21
+ assert rate == 1.0
tests/unit/test_fd_utils.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
4
+
5
+ from utils import fd_utils
6
+ from datetime import date
7
+
8
+ def test_calculate_fd_returns():
9
+ result = fd_utils.calculate_fd_returns(1000, 0.05, date(2020, 1, 1), date(2021, 1, 1))
10
+ # 1 year at 5% interest
11
+ assert abs(result - 1050) < 1
tests/unit/test_yfinance_utils.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
4
+
5
+ import pytest
6
+ from unittest.mock import patch, MagicMock
7
+ from utils import yfinance_utils
8
+ import pandas as pd
9
+
10
+ def test_fetch_yfinance_daily_success():
11
+ with patch('yfinance.download') as mock_download:
12
+ df = pd.DataFrame({'Adj Close': [1, 2, 3]}, index=pd.date_range('2020-01-01', periods=3))
13
+ mock_download.return_value = df
14
+ result = yfinance_utils.fetch_yfinance_daily('MSFT', '2020-01-01', '2020-01-03')
15
+ assert isinstance(result, pd.Series)
16
+ assert not result.empty
17
+
18
+ def test_fetch_yfinance_daily_empty():
19
+ with patch('yfinance.download') as mock_download:
20
+ df = pd.DataFrame()
21
+ mock_download.return_value = df
22
+ result = yfinance_utils.fetch_yfinance_daily('MSFT', '2020-01-01', '2020-01-03')
23
+ assert result is None
utils/currency_utils.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import yfinance as yf
2
+
3
+
4
+ def get_usd_sgd_rate():
5
+ try:
6
+ fx = yf.download('USDSGD=X', period='5d', interval='1d')
7
+ if not fx.empty:
8
+ return float(fx['Close'][-1])
9
+ except Exception:
10
+ pass
11
+ return 1.0 # fallback to 1 if failed
utils/fd_utils.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ def calculate_fd_returns(initial_amount, rate, start_date, end_date):
2
+ days = (end_date - start_date).days
3
+ years = days / 365
4
+ return initial_amount * (1 + rate) ** years
utils/yfinance_utils.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import yfinance as yf
2
+ import pandas as pd
3
+
4
+ def fetch_yfinance_daily(ticker, start_date, end_date):
5
+ try:
6
+ data = yf.download(ticker, start=start_date, end=end_date)
7
+ if data.empty:
8
+ print(f"No data found for {ticker} between {start_date} and {end_date}")
9
+ return None
10
+ print("data type returned:", type(data['Close']))
11
+ return data['Close'][ticker]
12
+ except Exception:
13
+ return None
14
+
15
+ if __name__ == "__main__":
16
+ print(fetch_yfinance_daily("MSFT", "2020-01-01", "2020-01-10"))