luisotorres commited on
Commit
6f0d9f0
Β·
verified Β·
1 Parent(s): 81002b3

Upload 6 files

Browse files
Files changed (6) hide show
  1. .gitattributes +35 -35
  2. README.md +58 -58
  3. app.py +25 -12
  4. functions.py +329 -329
  5. requirements.txt +5 -5
  6. ui.py +140 -108
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,58 +1,58 @@
1
- ---
2
- title: PortfolioPro
3
- emoji: πŸ’°
4
- colorFrom: gray
5
- colorTo: purple
6
- sdk: streamlit
7
- sdk_version: 1.31.1
8
- app_file: app.py
9
- pinned: false
10
- license: apache-2.0
11
- ---
12
-
13
- <h1>PortfolioPro πŸ’°</h1>
14
-
15
- This intuitive demo app is built to help you with your investment tracking and analysis. Effortlessly monitor your assets, benchmark against market standards, and discover valuable insights with just a few clicks.
16
-
17
- Here's what you can do with this app:
18
-
19
- β€’ Enter the ticker symbols and the total amount invested for each security in your portfolio.
20
-
21
- β€’ Set a benchmark to compare your portfolio's performance against market indices or other chosen standards.
22
-
23
- β€’ Select the start and end dates for the period you wish to analyze and gain historical insights.
24
-
25
- β€’ Click "Run Analysis" to visualize historical returns, obtain volatility metrics, and unveil the allocation percentages of your portfolio.
26
-
27
- Empower your investment strategy with cutting-edge financial APIs and visualization tools.<br>
28
- Start making informed decisions to elevate your financial future today.
29
-
30
- If you have any questions or suggestions, feel free to contact me at any of my social media pages.
31
-
32
- *Thank you!*
33
-
34
- <hr style="border: 0;
35
- height: 1px;
36
- border-top: 0.85px;
37
- solid #b2b2b2">
38
-
39
- <div style="text-align: left;
40
- color: #8d8d8d;
41
- padding-left: 15px;
42
- font-size: 14.25px;">
43
- Luis Fernando Torres, 2024<br><br>
44
- Let's connect!πŸ”—<br>
45
- <a href="https://www.linkedin.com/in/luuisotorres/">LinkedIn</a> β€’ <a href="https://medium.com/@luuisotorres">Medium</a> β€’ <a href = "https://www.kaggle.com/lusfernandotorres/code">Kaggle</a><br><br>
46
- </div>
47
- <div style="text-align: center;
48
- margin-top: 50px;
49
- color: #8d8d8d;
50
- padding-left: 15px;
51
- font-size: 14.25px;"><b>Like my content? Feel free to <a href="https://www.buymeacoffee.com/luuisotorres">Buy Me a Coffee β˜•</a></b>
52
- </div>
53
- <div style="text-align: center;
54
- margin-top: 80px;
55
- color: #8d8d8d;
56
- padding-left: 15px;
57
- font-size: 14.25px;"><b> <a href = "https://luuisotorres.github.io/">https://luuisotorres.github.io/</a> </b>
58
- </div>
 
1
+ ---
2
+ title: PortfolioPro
3
+ emoji: πŸ’°
4
+ colorFrom: gray
5
+ colorTo: purple
6
+ sdk: streamlit
7
+ sdk_version: 1.31.1
8
+ app_file: app.py
9
+ pinned: false
10
+ license: apache-2.0
11
+ ---
12
+
13
+ <h1>PortfolioPro πŸ’°</h1>
14
+
15
+ This intuitive demo app is built to help you with your investment tracking and analysis. Effortlessly monitor your assets, benchmark against market standards, and discover valuable insights with just a few clicks.
16
+
17
+ Here's what you can do with this app:
18
+
19
+ β€’ Enter the ticker symbols and the total amount invested for each security in your portfolio.
20
+
21
+ β€’ Set a benchmark to compare your portfolio's performance against market indices or other chosen standards.
22
+
23
+ β€’ Select the start and end dates for the period you wish to analyze and gain historical insights.
24
+
25
+ β€’ Click "Run Analysis" to visualize historical returns, obtain volatility metrics, and unveil the allocation percentages of your portfolio.
26
+
27
+ Empower your investment strategy with cutting-edge financial APIs and visualization tools.<br>
28
+ Start making informed decisions to elevate your financial future today.
29
+
30
+ If you have any questions or suggestions, feel free to contact me at any of my social media pages.
31
+
32
+ *Thank you!*
33
+
34
+ <hr style="border: 0;
35
+ height: 1px;
36
+ border-top: 0.85px;
37
+ solid #b2b2b2">
38
+
39
+ <div style="text-align: left;
40
+ color: #8d8d8d;
41
+ padding-left: 15px;
42
+ font-size: 14.25px;">
43
+ Luis Fernando Torres, 2024<br><br>
44
+ Let's connect!πŸ”—<br>
45
+ <a href="https://www.linkedin.com/in/luuisotorres/">LinkedIn</a> β€’ <a href="https://medium.com/@luuisotorres">Medium</a> β€’ <a href = "https://www.kaggle.com/lusfernandotorres/code">Kaggle</a><br><br>
46
+ </div>
47
+ <div style="text-align: center;
48
+ margin-top: 50px;
49
+ color: #8d8d8d;
50
+ padding-left: 15px;
51
+ font-size: 14.25px;"><b>Like my content? Feel free to <a href="https://www.buymeacoffee.com/luuisotorres">Buy Me a Coffee β˜•</a></b>
52
+ </div>
53
+ <div style="text-align: center;
54
+ margin-top: 80px;
55
+ color: #8d8d8d;
56
+ padding-left: 15px;
57
+ font-size: 14.25px;"><b> <a href = "https://luuisotorres.github.io/">https://luuisotorres.github.io/</a> </b>
58
+ </div>
app.py CHANGED
@@ -1,13 +1,26 @@
1
- import streamlit as st
2
- from ui import build_ui
3
-
4
- # Defining page settings
5
- st.set_page_config(
6
- page_title="Investment Portfolio Management",
7
- page_icon=":heavy_dollar_sign:",
8
- layout='wide',
9
- initial_sidebar_state='expanded'
10
- )
11
-
12
- # Build the UI
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  build_ui()
 
1
+ import streamlit as st
2
+ from ui import build_ui
3
+
4
+ # Defining page settings
5
+ st.set_page_config(
6
+ page_title="PortfolioPro",
7
+ page_icon="πŸ’°",
8
+ layout='wide',
9
+ initial_sidebar_state='expanded'
10
+ )
11
+
12
+ # Set custom theme
13
+ st.markdown("""
14
+ <style>
15
+ :root {
16
+ --primary-color: #7792E3;
17
+ --background-color: #FFFFFF;
18
+ --secondary-background-color: #F0F2F6;
19
+ --text-color: #31333F;
20
+ --font: sans-serif;
21
+ }
22
+ </style>
23
+ """, unsafe_allow_html=True)
24
+
25
+ # Build the UI
26
  build_ui()
functions.py CHANGED
@@ -1,330 +1,330 @@
1
- # Importing necessary libraries
2
- import streamlit as st
3
- from datetime import date
4
- import yfinance as yf
5
- import numpy as np
6
- import pandas as pd
7
- import plotly.express as px
8
- import plotly.graph_objs as go
9
- import plotly.subplots as sp
10
- from plotly.subplots import make_subplots
11
- import plotly.figure_factory as ff
12
- import plotly.io as pio
13
- from IPython.display import display
14
- from plotly.offline import init_notebook_mode
15
- init_notebook_mode(connected=True)
16
-
17
- # Hiding Warnings
18
- import warnings
19
- warnings.filterwarnings('ignore')
20
-
21
- def perform_portfolio_analysis(df, tickers_weights):
22
- """
23
- This function takes historical stock data and the weights of the securities in the portfolio,
24
- It calculates individual security returns, cumulative returns, volatility, and Sharpe Ratios.
25
- It then visualizes this data, showing historical performance and a risk-reward plot.
26
-
27
- Parameters:
28
- - df (pd.DataFrame): DataFrame containing historical stock data with securities as columns.
29
- - tickers_weights (dict): A dictionary where keys are ticker symbols (str) and values are their
30
- respective weights (float)in the portfolio.
31
-
32
- Returns:
33
- - fig1: A Plotly Figure with two subplots:
34
- 1. Line plot showing the historical returns of each security in the portfolio.
35
- 2. Plot showing the annualized volatility and last cumulative return of each security
36
- colored by their respective Sharpe Ratio.
37
-
38
- Notes:
39
- - The function assumes that 'pandas', 'numpy', and 'plotly.graph_objects' are imported as 'pd', 'np', and 'go' respectively.
40
- - The function also utilizes 'plotly.subplots.make_subplots' for creating subplots.
41
- - The risk-free rate is assumed to be 1% per annum for Sharpe Ratio calculation.
42
- """
43
-
44
- # Starting DataFrame and Series
45
- individual_cumsum = pd.DataFrame()
46
- individual_vol = pd.Series(dtype=float)
47
- individual_sharpe = pd.Series(dtype=float)
48
-
49
-
50
- # Iterating through tickers and weights in the tickers_weights dictionary
51
- for ticker, weight in tickers_weights.items():
52
- if ticker in df.columns: # Confirming that the tickers are available
53
- individual_returns = df[ticker].pct_change() # Computing individual daily returns for each ticker
54
- individual_cumsum[ticker] = ((1 + individual_returns).cumprod() - 1) * 100 # Computing cumulative returns over the period for each ticker
55
- vol = (individual_returns.std() * np.sqrt(252)) * 100 # Computing annualized volatility
56
- individual_vol[ticker] = vol # Adding annualized volatility for each ticker
57
- individual_excess_returns = individual_returns - 0.01 / 252 # Computing the excess returns
58
- sharpe = (individual_excess_returns.mean() / individual_returns.std() * np.sqrt(252)).round(2) # Computing Sharpe Ratio
59
- individual_sharpe[ticker] = sharpe # Adding Sharpe Ratio for each ticker
60
-
61
- # Creating subplots for comparison across securities
62
- fig1 = make_subplots(rows = 1, cols = 2, horizontal_spacing=0.25,
63
- column_titles=['Historical Performance Assets', 'Risk-Reward'],
64
- column_widths=[.55, .45],
65
- shared_xaxes=False, shared_yaxes=False)
66
-
67
- # Adding the historical returns for each ticker on the first subplot
68
- for ticker in individual_cumsum.columns:
69
- fig1.add_trace(go.Scatter(x=individual_cumsum.index,
70
- y=individual_cumsum[ticker],
71
- mode = 'lines',
72
- name = ticker,
73
- hovertemplate = '%{y:.2f}%',
74
- showlegend=True),
75
- row=1, col=1)
76
-
77
- # Defining colors for markers on the second subplot
78
- sharpe_colors = [individual_sharpe[ticker] for ticker in individual_cumsum.columns]
79
-
80
- # Adding markers for each ticker on the second subplot
81
- fig1.add_trace(go.Scatter(x=individual_vol.tolist(),
82
- y=individual_cumsum.iloc[-1].tolist(),
83
- mode='markers+text',
84
- marker=dict(size=75, color = sharpe_colors,
85
- colorscale = 'Bluered_r',
86
- colorbar=dict(title='Sharpe Ratio'),
87
- showscale=True),
88
- name = 'Returns',
89
- text = individual_cumsum.columns.tolist(),
90
- textfont=dict(color='white'),
91
- showlegend=False,
92
- hovertemplate = '%{y:.2f}%<br>Annualized Volatility: %{x:.2f}%<br>Sharpe Ratio: %{marker.color:.2f}',
93
- textposition='middle center'),
94
- row=1, col=2)
95
-
96
- # Updating layout
97
- fig1.update_layout(title={
98
- 'text': f'<b>Portfolio Analysis</b>',
99
- 'font': {'size': 24}
100
- },
101
- template = 'plotly_white',
102
- height = 650, width = 1250,
103
- hovermode = 'x unified',
104
- legend_x=.45,
105
- legend_y=.5)
106
-
107
- fig1.update_yaxes(title_text='Returns (%)', col=1)
108
- fig1.update_yaxes(title_text='Returns (%)', col = 2)
109
- fig1.update_xaxes(title_text = 'Date', col = 1)
110
- fig1.update_xaxes(title_text = 'Annualized Volatility (%)', col =2)
111
-
112
- return fig1 # Returning figure
113
-
114
-
115
- # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
116
- # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
117
- # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
118
-
119
-
120
- def portfolio_vs_benchmark(port_returns, benchmark_returns):
121
-
122
- """
123
- This function calculates and displays the cumulative returns, annualized volatility, and Sharpe Ratios
124
- for both the portfolio and the benchmark. It provides a side-by-side comparison to assess the portfolio's
125
- performance relative to the benchmark.
126
-
127
- Parameters:
128
- - port_returns (pd.Series): A Pandas Series containing the daily returns of the portfolio.
129
- - benchmark_returns (pd.Series): A Pandas Series containing the daily returns of the benchmark.
130
-
131
- Returns:
132
- - fig2: A Plotly Figure object with two subplots:
133
- 1. Line plot showing the cumulative returns of both the portfolio and the benchmark over time.
134
- 2. Scatter plot indicating the annualized volatility and the last cumulative return of both the portfolio
135
- and the benchmark, colored by their respective Sharpe Ratios.
136
-
137
- Notes:
138
- - The function assumes that 'numpy' and 'plotly.graph_objects' are imported as 'np' and 'go' respectively.
139
- - The function also utilizes 'plotly.subplots.make_subplots' for creating subplots.
140
- - The risk-free rate is assumed to be 1% per annum for Sharpe Ratio calculation.
141
- """
142
-
143
- # Computing the cumulative returns for the portfolio and the benchmark
144
- portfolio_cumsum = (((1 + port_returns).cumprod() - 1) * 100).round(2)
145
- benchmark_cumsum = (((1 + benchmark_returns).cumprod() - 1) * 100).round(2)
146
-
147
- # Computing the annualized volatility for the portfolio and the benchmark
148
- port_vol = ((port_returns.std() * np.sqrt(252)) * 100).round(2)
149
- benchmark_vol = ((benchmark_returns.std() * np.sqrt(252)) * 100).round(2)
150
-
151
- # Computing Sharpe Ratio for the portfolio and the benchmark
152
- excess_port_returns = port_returns - 0.01 / 252
153
- port_sharpe = (excess_port_returns.mean() / port_returns.std() * np.sqrt(252)).round(2)
154
- exces_benchmark_returns = benchmark_returns - 0.01 / 252
155
- benchmark_sharpe = (exces_benchmark_returns.mean() / benchmark_returns.std() * np.sqrt(252)).round(2)
156
-
157
- # Creating a subplot to compare portfolio performance with the benchmark
158
- fig2 = make_subplots(rows = 1, cols = 2, horizontal_spacing=0.25,
159
- column_titles=['Cumulative Returns', 'Portfolio Risk-Reward'],
160
- column_widths=[.55, .45],
161
- shared_xaxes=False, shared_yaxes=False)
162
-
163
- # Adding the cumulative returns for the portfolio
164
- fig2.add_trace(go.Scatter(x=portfolio_cumsum.index,
165
- y = portfolio_cumsum,
166
- mode = 'lines', name = 'Portfolio', showlegend=False,
167
- hovertemplate = '%{y:.2f}%'),
168
- row=1,col=1)
169
-
170
- # Adding the cumulative returns for the benchmark
171
- fig2.add_trace(go.Scatter(x=benchmark_cumsum.index,
172
- y = benchmark_cumsum,
173
- mode = 'lines', name = 'Benchmark', showlegend=False,
174
- hovertemplate = '%{y:.2f}%'),
175
- row=1,col=1)
176
-
177
-
178
- # Creating risk-reward plot for the benchmark and the portfolio
179
- fig2.add_trace(go.Scatter(x = [port_vol, benchmark_vol], y = [portfolio_cumsum.iloc[-1], benchmark_cumsum.iloc[-1]],
180
- mode = 'markers+text',
181
- marker=dict(size = 75,
182
- color = [port_sharpe, benchmark_sharpe],
183
- colorscale='Bluered_r',
184
- colorbar=dict(title='Sharpe Ratio'),
185
- showscale=True),
186
- name = 'Returns',
187
- text=['Portfolio', 'Benchmark'], textposition='middle center',
188
- textfont=dict(color='white'),
189
- hovertemplate = '%{y:.2f}%<br>Annualized Volatility: %{x:.2f}%<br>Sharpe Ratio: %{marker.color:.2f}',
190
- showlegend=False),
191
- row = 1, col = 2)
192
-
193
-
194
- # Configuring layout
195
- fig2.update_layout(title={
196
- 'text': f'<b>Portfolio vs Benchmark</b>',
197
- 'font': {'size': 24}
198
- },
199
- template = 'plotly_white',
200
- height = 650, width = 1250,
201
- hovermode = 'x unified',
202
- #legend_x=.45,
203
- #legend_y=.5
204
- )
205
-
206
- fig2.update_yaxes(title_text='Cumulative Returns (%)', col=1)
207
- fig2.update_yaxes(title_text='Cumulative Returns (%)', col = 2)
208
- fig2.update_xaxes(title_text = 'Date', col = 1)
209
- fig2.update_xaxes(title_text = 'Annualized Volatility (%)', col =2)
210
-
211
- return fig2 # Returning subplots
212
-
213
- # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
214
- # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
215
- # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
216
-
217
-
218
- def portfolio_returns(tickers_and_values, start_date, end_date, benchmark):
219
-
220
- """
221
- This function downloads historical stock data, calculates the weighted returns to build a portfolio,
222
- and compares these returns to a benchmark.
223
- It also displays the portfolio allocation and the performance of the portfolio against the benchmark.
224
-
225
- Parameters:
226
- - tickers_and_values (dict): A dictionary where keys are ticker symbols (str) and values are the current
227
- amounts (float) invested in each ticker.
228
- - start_date (str): The start date for the historical data in the format 'YYYY-MM-DD'.
229
- - end_date (str): The end date for the historical data in the format 'YYYY-MM-DD'.
230
- - benchmark (str): The ticker symbol for the benchmark against which to compare the portfolio's performance.
231
-
232
- Returns:
233
- - Displays three plots:
234
- 1. A pie chart showing the portfolio allocation by ticker.
235
- 2. A plot to analyze historical returns and volatility of each security
236
- in the portfolio. (Not plotted if portfolio only has one security)
237
- 2. A comparison between portfolio returns and volatility against the benchmark over the specified period.
238
-
239
- Notes:
240
- - The function assumes that 'yfinance', 'pandas', 'plotly.graph_objects', and 'plotly.express' are imported
241
- as 'yf', 'pd', 'go', and 'px' respectively.
242
- - For single security portfolios, the function calculates returns without weighting.
243
- - The function utilizes a helper function 'portfolio_vs_benchmark' for comparing portfolio returns with
244
- the benchmark, which needs to be defined separately.
245
- - Another helper function 'perform_portfolio_analysis' is called for portfolios with more than one security,
246
- which also needs to be defined separately.
247
- """
248
-
249
- # Obtaining tickers data with yfinance
250
- df = yf.download(tickers=list(tickers_and_values.keys()),
251
- start=start_date, end=end_date)
252
-
253
- # Checking if there is data available in the given date range
254
- if isinstance(df.columns, pd.MultiIndex):
255
- missing_data_tickers = []
256
- for ticker in tickers_and_values.keys():
257
- first_valid_index = df['Adj Close'][ticker].first_valid_index()
258
- if first_valid_index is None or first_valid_index.strftime('%Y-%m-%d') > start_date:
259
- missing_data_tickers.append(ticker)
260
-
261
- if missing_data_tickers:
262
- error_message = f"No data available for the following tickers starting from {start_date}: {', '.join(missing_data_tickers)}"
263
- return "error", error_message
264
- else:
265
- # For a single ticker, simply check the first valid index
266
- first_valid_index = df['Adj Close'].first_valid_index()
267
- if first_valid_index is None or first_valid_index.strftime('%Y-%m-%d') > start_date:
268
- error_message = f"No data available for the ticker starting from {start_date}"
269
- return "error", error_message
270
-
271
- # Calculating portfolio value
272
- total_portfolio_value = sum(tickers_and_values.values())
273
-
274
- # Calculating the weights for each security in the portfolio
275
- tickers_weights = {ticker: value / total_portfolio_value for ticker, value in tickers_and_values.items()}
276
-
277
- # Checking if dataframe has MultiIndex columns
278
- if isinstance(df.columns, pd.MultiIndex):
279
- df = df['Adj Close'].fillna(df['Close']) # If 'Adjusted Close' is not available, use 'Close'
280
-
281
- # Checking if there are more than just one security in the portfolio
282
- if len(tickers_weights) > 1:
283
- weights = list(tickers_weights.values()) # Obtaining weights
284
- weighted_returns = df.pct_change().mul(weights, axis = 1) # Computed weighted returns
285
- port_returns = weighted_returns.sum(axis=1) # Sum weighted returns to build portfolio returns
286
- # If there is only one security in the portfolio...
287
- else:
288
- df = df['Adj Close'].fillna(df['Close']) # Obtaining 'Adjusted Close'. If not available, use 'Close'
289
- port_returns = df.pct_change() # Computing returns without weights
290
-
291
- # Obtaining benchmark data with yfinance
292
- benchmark_df = yf.download(benchmark,
293
- start=start_date, end=end_date)
294
- # Obtaining 'Adjusted Close'. If not available, use 'Close'.
295
- benchmark_df = benchmark_df['Adj Close'].fillna(benchmark_df['Close'])
296
-
297
- # Computing benchmark returns
298
- benchmark_returns = benchmark_df.pct_change()
299
-
300
-
301
- # Plotting a pie plot
302
- fig = go.Figure(data=[go.Pie(
303
- labels=list(tickers_weights.keys()), # Obtaining tickers
304
- values=list(tickers_weights.values()), # Obtaining weights
305
- hoverinfo='label+percent',
306
- textinfo='label+percent',
307
- hole=.65,
308
- marker=dict(colors=px.colors.qualitative.G10)
309
- )])
310
-
311
- # Defining layout
312
- fig.update_layout(title={
313
- 'text': '<b>Portfolio Allocation</b>',
314
- 'font': {'size': 24}
315
- }, height=550, width=1250)
316
-
317
- # Running function to compare portfolio and benchmark
318
- fig2 = portfolio_vs_benchmark(port_returns, benchmark_returns)
319
-
320
- #fig.show() # Displaying Portfolio Allocation plot
321
-
322
- # If we have more than one security in the portfolio,
323
- # we run function to evaluate each security individually
324
- fig1 = None
325
- if len(tickers_weights) > 1:
326
- fig1 = perform_portfolio_analysis(df, tickers_weights)
327
- #fig1.show()
328
- # Displaying Portfolio vs Benchmark plot
329
- #fig2.show()
330
  return "success", (fig, fig1, fig2)
 
1
+ # Importing necessary libraries
2
+ import streamlit as st
3
+ from datetime import date
4
+ import yfinance as yf
5
+ import numpy as np
6
+ import pandas as pd
7
+ import plotly.express as px
8
+ import plotly.graph_objs as go
9
+ import plotly.subplots as sp
10
+ from plotly.subplots import make_subplots
11
+ import plotly.figure_factory as ff
12
+ import plotly.io as pio
13
+ from IPython.display import display
14
+ from plotly.offline import init_notebook_mode
15
+ init_notebook_mode(connected=True)
16
+
17
+ # Hiding Warnings
18
+ import warnings
19
+ warnings.filterwarnings('ignore')
20
+
21
+ def perform_portfolio_analysis(df, tickers_weights):
22
+ """
23
+ This function takes historical stock data and the weights of the securities in the portfolio,
24
+ It calculates individual security returns, cumulative returns, volatility, and Sharpe Ratios.
25
+ It then visualizes this data, showing historical performance and a risk-reward plot.
26
+
27
+ Parameters:
28
+ - df (pd.DataFrame): DataFrame containing historical stock data with securities as columns.
29
+ - tickers_weights (dict): A dictionary where keys are ticker symbols (str) and values are their
30
+ respective weights (float)in the portfolio.
31
+
32
+ Returns:
33
+ - fig1: A Plotly Figure with two subplots:
34
+ 1. Line plot showing the historical returns of each security in the portfolio.
35
+ 2. Plot showing the annualized volatility and last cumulative return of each security
36
+ colored by their respective Sharpe Ratio.
37
+
38
+ Notes:
39
+ - The function assumes that 'pandas', 'numpy', and 'plotly.graph_objects' are imported as 'pd', 'np', and 'go' respectively.
40
+ - The function also utilizes 'plotly.subplots.make_subplots' for creating subplots.
41
+ - The risk-free rate is assumed to be 1% per annum for Sharpe Ratio calculation.
42
+ """
43
+
44
+ # Starting DataFrame and Series
45
+ individual_cumsum = pd.DataFrame()
46
+ individual_vol = pd.Series(dtype=float)
47
+ individual_sharpe = pd.Series(dtype=float)
48
+
49
+
50
+ # Iterating through tickers and weights in the tickers_weights dictionary
51
+ for ticker, weight in tickers_weights.items():
52
+ if ticker in df.columns: # Confirming that the tickers are available
53
+ individual_returns = df[ticker].pct_change() # Computing individual daily returns for each ticker
54
+ individual_cumsum[ticker] = ((1 + individual_returns).cumprod() - 1) * 100 # Computing cumulative returns over the period for each ticker
55
+ vol = (individual_returns.std() * np.sqrt(252)) * 100 # Computing annualized volatility
56
+ individual_vol[ticker] = vol # Adding annualized volatility for each ticker
57
+ individual_excess_returns = individual_returns - 0.01 / 252 # Computing the excess returns
58
+ sharpe = (individual_excess_returns.mean() / individual_returns.std() * np.sqrt(252)).round(2) # Computing Sharpe Ratio
59
+ individual_sharpe[ticker] = sharpe # Adding Sharpe Ratio for each ticker
60
+
61
+ # Creating subplots for comparison across securities
62
+ fig1 = make_subplots(rows = 1, cols = 2, horizontal_spacing=0.25,
63
+ column_titles=['Historical Performance Assets', 'Risk-Reward'],
64
+ column_widths=[.55, .45],
65
+ shared_xaxes=False, shared_yaxes=False)
66
+
67
+ # Adding the historical returns for each ticker on the first subplot
68
+ for ticker in individual_cumsum.columns:
69
+ fig1.add_trace(go.Scatter(x=individual_cumsum.index,
70
+ y=individual_cumsum[ticker],
71
+ mode = 'lines',
72
+ name = ticker,
73
+ hovertemplate = '%{y:.2f}%',
74
+ showlegend=True),
75
+ row=1, col=1)
76
+
77
+ # Defining colors for markers on the second subplot
78
+ sharpe_colors = [individual_sharpe[ticker] for ticker in individual_cumsum.columns]
79
+
80
+ # Adding markers for each ticker on the second subplot
81
+ fig1.add_trace(go.Scatter(x=individual_vol.tolist(),
82
+ y=individual_cumsum.iloc[-1].tolist(),
83
+ mode='markers+text',
84
+ marker=dict(size=75, color = sharpe_colors,
85
+ colorscale = 'Bluered_r',
86
+ colorbar=dict(title='Sharpe Ratio'),
87
+ showscale=True),
88
+ name = 'Returns',
89
+ text = individual_cumsum.columns.tolist(),
90
+ textfont=dict(color='white'),
91
+ showlegend=False,
92
+ hovertemplate = '%{y:.2f}%<br>Annualized Volatility: %{x:.2f}%<br>Sharpe Ratio: %{marker.color:.2f}',
93
+ textposition='middle center'),
94
+ row=1, col=2)
95
+
96
+ # Updating layout
97
+ fig1.update_layout(title={
98
+ 'text': f'<b>Portfolio Analysis</b>',
99
+ 'font': {'size': 24}
100
+ },
101
+ template = 'plotly_white',
102
+ height = 650, width = 1250,
103
+ hovermode = 'x unified',
104
+ legend_x=.45,
105
+ legend_y=.5)
106
+
107
+ fig1.update_yaxes(title_text='Returns (%)', col=1)
108
+ fig1.update_yaxes(title_text='Returns (%)', col = 2)
109
+ fig1.update_xaxes(title_text = 'Date', col = 1)
110
+ fig1.update_xaxes(title_text = 'Annualized Volatility (%)', col =2)
111
+
112
+ return fig1 # Returning figure
113
+
114
+
115
+ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
116
+ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
117
+ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
118
+
119
+
120
+ def portfolio_vs_benchmark(port_returns, benchmark_returns):
121
+
122
+ """
123
+ This function calculates and displays the cumulative returns, annualized volatility, and Sharpe Ratios
124
+ for both the portfolio and the benchmark. It provides a side-by-side comparison to assess the portfolio's
125
+ performance relative to the benchmark.
126
+
127
+ Parameters:
128
+ - port_returns (pd.Series): A Pandas Series containing the daily returns of the portfolio.
129
+ - benchmark_returns (pd.Series): A Pandas Series containing the daily returns of the benchmark.
130
+
131
+ Returns:
132
+ - fig2: A Plotly Figure object with two subplots:
133
+ 1. Line plot showing the cumulative returns of both the portfolio and the benchmark over time.
134
+ 2. Scatter plot indicating the annualized volatility and the last cumulative return of both the portfolio
135
+ and the benchmark, colored by their respective Sharpe Ratios.
136
+
137
+ Notes:
138
+ - The function assumes that 'numpy' and 'plotly.graph_objects' are imported as 'np' and 'go' respectively.
139
+ - The function also utilizes 'plotly.subplots.make_subplots' for creating subplots.
140
+ - The risk-free rate is assumed to be 1% per annum for Sharpe Ratio calculation.
141
+ """
142
+
143
+ # Computing the cumulative returns for the portfolio and the benchmark
144
+ portfolio_cumsum = (((1 + port_returns).cumprod() - 1) * 100).round(2)
145
+ benchmark_cumsum = (((1 + benchmark_returns).cumprod() - 1) * 100).round(2)
146
+
147
+ # Computing the annualized volatility for the portfolio and the benchmark
148
+ port_vol = ((port_returns.std() * np.sqrt(252)) * 100).round(2)
149
+ benchmark_vol = ((benchmark_returns.std() * np.sqrt(252)) * 100).round(2)
150
+
151
+ # Computing Sharpe Ratio for the portfolio and the benchmark
152
+ excess_port_returns = port_returns - 0.01 / 252
153
+ port_sharpe = (excess_port_returns.mean() / port_returns.std() * np.sqrt(252)).round(2)
154
+ exces_benchmark_returns = benchmark_returns - 0.01 / 252
155
+ benchmark_sharpe = (exces_benchmark_returns.mean() / benchmark_returns.std() * np.sqrt(252)).round(2)
156
+
157
+ # Creating a subplot to compare portfolio performance with the benchmark
158
+ fig2 = make_subplots(rows = 1, cols = 2, horizontal_spacing=0.25,
159
+ column_titles=['Cumulative Returns', 'Portfolio Risk-Reward'],
160
+ column_widths=[.55, .45],
161
+ shared_xaxes=False, shared_yaxes=False)
162
+
163
+ # Adding the cumulative returns for the portfolio
164
+ fig2.add_trace(go.Scatter(x=portfolio_cumsum.index,
165
+ y = portfolio_cumsum,
166
+ mode = 'lines', name = 'Portfolio', showlegend=False,
167
+ hovertemplate = '%{y:.2f}%'),
168
+ row=1,col=1)
169
+
170
+ # Adding the cumulative returns for the benchmark
171
+ fig2.add_trace(go.Scatter(x=benchmark_cumsum.index,
172
+ y = benchmark_cumsum,
173
+ mode = 'lines', name = 'Benchmark', showlegend=False,
174
+ hovertemplate = '%{y:.2f}%'),
175
+ row=1,col=1)
176
+
177
+
178
+ # Creating risk-reward plot for the benchmark and the portfolio
179
+ fig2.add_trace(go.Scatter(x = [port_vol, benchmark_vol], y = [portfolio_cumsum.iloc[-1], benchmark_cumsum.iloc[-1]],
180
+ mode = 'markers+text',
181
+ marker=dict(size = 75,
182
+ color = [port_sharpe, benchmark_sharpe],
183
+ colorscale='Bluered_r',
184
+ colorbar=dict(title='Sharpe Ratio'),
185
+ showscale=True),
186
+ name = 'Returns',
187
+ text=['Portfolio', 'Benchmark'], textposition='middle center',
188
+ textfont=dict(color='white'),
189
+ hovertemplate = '%{y:.2f}%<br>Annualized Volatility: %{x:.2f}%<br>Sharpe Ratio: %{marker.color:.2f}',
190
+ showlegend=False),
191
+ row = 1, col = 2)
192
+
193
+
194
+ # Configuring layout
195
+ fig2.update_layout(title={
196
+ 'text': f'<b>Portfolio vs Benchmark</b>',
197
+ 'font': {'size': 24}
198
+ },
199
+ template = 'plotly_white',
200
+ height = 650, width = 1250,
201
+ hovermode = 'x unified',
202
+ #legend_x=.45,
203
+ #legend_y=.5
204
+ )
205
+
206
+ fig2.update_yaxes(title_text='Cumulative Returns (%)', col=1)
207
+ fig2.update_yaxes(title_text='Cumulative Returns (%)', col = 2)
208
+ fig2.update_xaxes(title_text = 'Date', col = 1)
209
+ fig2.update_xaxes(title_text = 'Annualized Volatility (%)', col =2)
210
+
211
+ return fig2 # Returning subplots
212
+
213
+ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
214
+ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
215
+ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
216
+
217
+
218
+ def portfolio_returns(tickers_and_values, start_date, end_date, benchmark):
219
+
220
+ """
221
+ This function downloads historical stock data, calculates the weighted returns to build a portfolio,
222
+ and compares these returns to a benchmark.
223
+ It also displays the portfolio allocation and the performance of the portfolio against the benchmark.
224
+
225
+ Parameters:
226
+ - tickers_and_values (dict): A dictionary where keys are ticker symbols (str) and values are the current
227
+ amounts (float) invested in each ticker.
228
+ - start_date (str): The start date for the historical data in the format 'YYYY-MM-DD'.
229
+ - end_date (str): The end date for the historical data in the format 'YYYY-MM-DD'.
230
+ - benchmark (str): The ticker symbol for the benchmark against which to compare the portfolio's performance.
231
+
232
+ Returns:
233
+ - Displays three plots:
234
+ 1. A pie chart showing the portfolio allocation by ticker.
235
+ 2. A plot to analyze historical returns and volatility of each security
236
+ in the portfolio. (Not plotted if portfolio only has one security)
237
+ 2. A comparison between portfolio returns and volatility against the benchmark over the specified period.
238
+
239
+ Notes:
240
+ - The function assumes that 'yfinance', 'pandas', 'plotly.graph_objects', and 'plotly.express' are imported
241
+ as 'yf', 'pd', 'go', and 'px' respectively.
242
+ - For single security portfolios, the function calculates returns without weighting.
243
+ - The function utilizes a helper function 'portfolio_vs_benchmark' for comparing portfolio returns with
244
+ the benchmark, which needs to be defined separately.
245
+ - Another helper function 'perform_portfolio_analysis' is called for portfolios with more than one security,
246
+ which also needs to be defined separately.
247
+ """
248
+
249
+ # Obtaining tickers data with yfinance
250
+ df = yf.download(tickers=list(tickers_and_values.keys()),
251
+ start=start_date, end=end_date)
252
+
253
+ # Checking if there is data available in the given date range
254
+ if isinstance(df.columns, pd.MultiIndex):
255
+ missing_data_tickers = []
256
+ for ticker in tickers_and_values.keys():
257
+ first_valid_index = df['Adj Close'][ticker].first_valid_index()
258
+ if first_valid_index is None or first_valid_index.strftime('%Y-%m-%d') > start_date:
259
+ missing_data_tickers.append(ticker)
260
+
261
+ if missing_data_tickers:
262
+ error_message = f"No data available for the following tickers starting from {start_date}: {', '.join(missing_data_tickers)}"
263
+ return "error", error_message
264
+ else:
265
+ # For a single ticker, simply check the first valid index
266
+ first_valid_index = df['Adj Close'].first_valid_index()
267
+ if first_valid_index is None or first_valid_index.strftime('%Y-%m-%d') > start_date:
268
+ error_message = f"No data available for the ticker starting from {start_date}"
269
+ return "error", error_message
270
+
271
+ # Calculating portfolio value
272
+ total_portfolio_value = sum(tickers_and_values.values())
273
+
274
+ # Calculating the weights for each security in the portfolio
275
+ tickers_weights = {ticker: value / total_portfolio_value for ticker, value in tickers_and_values.items()}
276
+
277
+ # Checking if dataframe has MultiIndex columns
278
+ if isinstance(df.columns, pd.MultiIndex):
279
+ df = df['Adj Close'].fillna(df['Close']) # If 'Adjusted Close' is not available, use 'Close'
280
+
281
+ # Checking if there are more than just one security in the portfolio
282
+ if len(tickers_weights) > 1:
283
+ weights = list(tickers_weights.values()) # Obtaining weights
284
+ weighted_returns = df.pct_change().mul(weights, axis = 1) # Computed weighted returns
285
+ port_returns = weighted_returns.sum(axis=1) # Sum weighted returns to build portfolio returns
286
+ # If there is only one security in the portfolio...
287
+ else:
288
+ df = df['Adj Close'].fillna(df['Close']) # Obtaining 'Adjusted Close'. If not available, use 'Close'
289
+ port_returns = df.pct_change() # Computing returns without weights
290
+
291
+ # Obtaining benchmark data with yfinance
292
+ benchmark_df = yf.download(benchmark,
293
+ start=start_date, end=end_date)
294
+ # Obtaining 'Adjusted Close'. If not available, use 'Close'.
295
+ benchmark_df = benchmark_df['Adj Close'].fillna(benchmark_df['Close'])
296
+
297
+ # Computing benchmark returns
298
+ benchmark_returns = benchmark_df.pct_change()
299
+
300
+
301
+ # Plotting a pie plot
302
+ fig = go.Figure(data=[go.Pie(
303
+ labels=list(tickers_weights.keys()), # Obtaining tickers
304
+ values=list(tickers_weights.values()), # Obtaining weights
305
+ hoverinfo='label+percent',
306
+ textinfo='label+percent',
307
+ hole=.65,
308
+ marker=dict(colors=px.colors.qualitative.G10)
309
+ )])
310
+
311
+ # Defining layout
312
+ fig.update_layout(title={
313
+ 'text': '<b>Portfolio Allocation</b>',
314
+ 'font': {'size': 24}
315
+ }, height=550, width=1250)
316
+
317
+ # Running function to compare portfolio and benchmark
318
+ fig2 = portfolio_vs_benchmark(port_returns, benchmark_returns)
319
+
320
+ #fig.show() # Displaying Portfolio Allocation plot
321
+
322
+ # If we have more than one security in the portfolio,
323
+ # we run function to evaluate each security individually
324
+ fig1 = None
325
+ if len(tickers_weights) > 1:
326
+ fig1 = perform_portfolio_analysis(df, tickers_weights)
327
+ #fig1.show()
328
+ # Displaying Portfolio vs Benchmark plot
329
+ #fig2.show()
330
  return "success", (fig, fig1, fig2)
requirements.txt CHANGED
@@ -1,6 +1,6 @@
1
- numpy==1.26.1
2
- pandas==2.1.1
3
- plotly==5.17.0
4
- streamlit==1.31.0
5
- yfinance==0.2.31
6
  IPython
 
1
+ numpy==1.26.1
2
+ pandas==2.1.1
3
+ plotly==5.17.0
4
+ streamlit==1.31.0
5
+ yfinance==0.2.31
6
  IPython
ui.py CHANGED
@@ -1,108 +1,140 @@
1
- import streamlit as st
2
- from datetime import date
3
- from functions import perform_portfolio_analysis, portfolio_vs_benchmark, portfolio_returns
4
-
5
- def build_ui():
6
- """
7
- This function builds the Streamlit UI for the portfolio management app.
8
- """
9
-
10
- # Title and Introduction
11
- title = '<h1 style="font-family:Didot; font-size: 64px; text-align:left">PortfolioPro</h1>'
12
- st.markdown(title, unsafe_allow_html=True)
13
-
14
- text = """
15
- <p style="font-size: 18px; text-align: left;">
16
- <br>Welcome to <b>PortfolioPro</b>, an intuitive app that streamlines your investment portfolio management.
17
- Effortlessly monitor your assets, benchmark against market standards, and discover valuable insights with just a few clicks.
18
- Here's what you can do:
19
- <br><br>
20
- β€’ Enter the ticker symbols exactly as they appear on Yahoo Finance and the total amount invested for each security in your portfolio.<br><br>
21
- β€’ Set a benchmark to compare your portfolio's performance against market indices or other chosen standards.<br><br>
22
- β€’ Select the start and end dates for the period you wish to analyze and gain historical insights. <br>
23
- Note: The app cannot analyze dates before a company's IPO or use non-business days as your <i>start</i> or <i>end</i> dates.<br><br>
24
- β€’ Click "Run Analysis" to visualize historical returns, obtain volatility metrics, and unveil the allocation percentages of your portfolio.
25
- <br><br>
26
- Empower your investment strategy with cutting-edge financial APIs and visualization tools.
27
- <br>Start making informed decisions to elevate your financial future today.
28
- <br><br><br>
29
- Demo video: <a href="https://www.youtube.com/watch?v=7MuQ4G6tq_I">PortfolioPro - Demo</a>
30
- <br><br>
31
- Kaggle Notebook: <a href="https://www.kaggle.com/code/lusfernandotorres/building-an-investment-portfolio-management-app">Building an Investment Portfolio Management App πŸ’° - by @lusfernandotorres</a>
32
- <br><br>
33
- </p>
34
- """
35
- st.markdown(text, unsafe_allow_html=True)
36
-
37
- # Ticker and Value Input
38
- if 'num_pairs' not in st.session_state:
39
- st.session_state['num_pairs'] = 1
40
-
41
- def add_input_pair():
42
- st.session_state['num_pairs'] += 1
43
-
44
- tickers_and_values = {}
45
- for n in range(st.session_state['num_pairs']):
46
- col1, col2 = st.columns(2)
47
- with col1:
48
- ticker = st.text_input(f"Ticker {n+1}", key=f"ticker_{n+1}", placeholder="Enter the symbol for a security.")
49
- with col2:
50
- value = st.number_input(f"Value Invested in Ticker {n+1} ($)", min_value=0.0, format="%.2f", key=f"value_{n+1}")
51
- tickers_and_values[ticker] = value
52
-
53
- st.button("Add Another Ticker", on_click=add_input_pair)
54
-
55
- # Benchmark Input
56
- benchmark = st.text_input("Benchmark", placeholder="Enter the symbol for a benchmark.")
57
-
58
- # Date Input
59
- col1, col2 = st.columns(2)
60
- with col1:
61
- start_date = st.date_input("Start Date", value=date.today().replace(year=date.today().year - 1), min_value=date(1900, 1, 1))
62
- with col2:
63
- end_date = st.date_input("End Date", value=date.today(), min_value=date(1900, 1, 1))
64
-
65
- # Run Analysis Button
66
- if st.button("Run Analysis"):
67
- tickers_and_values = {k: v for k,v in tickers_and_values.items() if k and v > 0}
68
-
69
- if not benchmark:
70
- st.error("Please enter a benchmark ticker before running the analysis.")
71
- elif not tickers_and_values:
72
- st.error("Please add at least one ticker with a non-zero investment value before running the analysis.")
73
- else:
74
- start_date_str=start_date.strftime('%Y-%m-%d')
75
- end_date_str=end_date.strftime('%Y-%m-%d')
76
-
77
- status, result = portfolio_returns(tickers_and_values, start_date_str, end_date_str, benchmark)
78
-
79
- if status == "error":
80
- st.error(result)
81
- else:
82
- fig, fig1, fig2 = result
83
-
84
- if fig is not None:
85
- st.plotly_chart(fig)
86
- if fig1 is not None:
87
- st.plotly_chart(fig1)
88
- if fig2 is not None:
89
- st.plotly_chart(fig2)
90
-
91
- # Signature
92
- signature_html = """
93
- <hr style="border: 0; height: 1px; border-top: 0.85px solid #b2b2b2">
94
- <div style="text-align: left; color: #8d8d8d; padding-left: 15px; font-size: 14.25px;">
95
- Luis Fernando Torres, 2024<br><br>
96
- Let's connect!πŸ”—<br>
97
- <a href="https://www.linkedin.com/in/luuisotorres/" target="_blank">LinkedIn</a> β€’
98
- <a href="https://medium.com/@luuisotorres" target="_blank">Medium</a> β€’
99
- <a href="https://www.kaggle.com/lusfernandotorres" target="_blank">Kaggle</a><br><br>
100
- </div>
101
- <div style="text-align: center; margin-top: 50px; color: #8d8d8d; padding-left: 15px; font-size: 14.25px;">
102
- <b>Like my content? Feel free to <a href="https://www.buymeacoffee.com/luuisotorres" target="_blank">Buy Me a Coffee β˜•</a></b>
103
- </div>
104
- <div style="text-align: center; margin-top: 80px; color: #8d8d8d; padding-left: 15px; font-size: 14.25px;">
105
- <b><a href="https://luuisotorres.github.io/" target="_blank">https://luuisotorres.github.io/</a></b>
106
- </div>
107
- """
108
- st.markdown(signature_html, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from datetime import date
3
+ from functions import perform_portfolio_analysis, portfolio_vs_benchmark, portfolio_returns
4
+
5
+
6
+ def build_ui():
7
+ # Custom CSS
8
+ st.markdown("""
9
+ <style>
10
+ .big-font {
11
+ font-size:60px !important;
12
+ font-weight: bold;
13
+ color: #fffdfd;
14
+ }
15
+
16
+ .sidebar-header {
17
+ font-size:18px;
18
+ color: #fffdfd;
19
+ }
20
+
21
+ .description {
22
+ font-size:18px;
23
+ color: #fffdfd;
24
+ }
25
+
26
+ .subheader {
27
+ font-size: 25px;
28
+ color: #fffdfd;
29
+ }
30
+ .stButton>button {
31
+ color: #4F4F4F;
32
+ background-color: #E0E0E0;
33
+ border-radius: 5px;
34
+ }
35
+ </style>
36
+ """, unsafe_allow_html=True)
37
+
38
+ # Sidebar
39
+ with st.sidebar:
40
+ st.markdown('<p class="sidebar-header">PortfolioPro</p>', unsafe_allow_html=True)
41
+ st.markdown('<p class="subheader">πŸš€ Empower your investments</p>', unsafe_allow_html=True)
42
+ st.markdown("---")
43
+
44
+ # Ticker and Value Input
45
+ st.subheader("πŸ“Š Portfolio Composition")
46
+ if 'num_pairs' not in st.session_state:
47
+ st.session_state['num_pairs'] = 1
48
+
49
+ def add_input_pair():
50
+ st.session_state['num_pairs'] += 1
51
+
52
+ tickers_and_values = {}
53
+ for n in range(st.session_state['num_pairs']):
54
+ col1, col2 = st.columns(2)
55
+ with col1:
56
+ ticker = st.text_input(f"Ticker {n+1}", key=f"ticker_{n+1}", placeholder="e.g., AAPL")
57
+ with col2:
58
+ value = st.number_input(f"Value ($)", min_value=0.0, format="%.2f", key=f"value_{n+1}")
59
+ if ticker and value > 0:
60
+ tickers_and_values[ticker] = value
61
+
62
+ st.button("βž• Add Ticker", on_click=add_input_pair)
63
+
64
+ # Benchmark Input
65
+ st.markdown("---")
66
+ st.subheader("πŸ† Benchmark")
67
+ benchmark = st.text_input("Enter benchmark symbol", placeholder="e.g., SPY")
68
+
69
+ # Date Input
70
+ st.markdown("---")
71
+ st.subheader("πŸ“… Date Range")
72
+ start_date = st.date_input("Start Date", value=date.today().replace(year=date.today().year - 1), min_value=date(1900, 1, 1))
73
+ end_date = st.date_input("End Date", value=date.today(), min_value=date(1900, 1, 1))
74
+
75
+ # Run Analysis Button
76
+ st.markdown("---")
77
+ run_analysis = st.button("Run Analysis")
78
+
79
+ # Main content
80
+ st.markdown('<p class="big-font">PortfolioPro</p>', unsafe_allow_html=True)
81
+ st.markdown('<p class="description">An easy and simple way to keep track of your investment portfolio.</p>', unsafe_allow_html=True)
82
+
83
+ # Information boxes
84
+ col1, col2, col3 = st.columns(3)
85
+ with col1:
86
+ st.info("πŸ“ˆ Track Performance")
87
+ with col2:
88
+ st.info("πŸ” Analyze Risk")
89
+ with col3:
90
+ st.info("πŸ’‘ Gain Insights")
91
+
92
+ # Run Analysis
93
+ if run_analysis:
94
+ if not benchmark:
95
+ st.error("Please enter a benchmark ticker before running the analysis.")
96
+ elif not tickers_and_values:
97
+ st.error("Please add at least one ticker with a non-zero investment value before running the analysis.")
98
+ else:
99
+ start_date_str = start_date.strftime('%Y-%m-%d')
100
+ end_date_str = end_date.strftime('%Y-%m-%d')
101
+
102
+ with st.spinner('Analyzing your portfolio...'):
103
+ status, result = portfolio_returns(tickers_and_values, start_date_str, end_date_str, benchmark)
104
+
105
+ if status == "error":
106
+ st.error(result)
107
+ else:
108
+ fig, fig1, fig2 = result
109
+
110
+ if fig is not None:
111
+ st.plotly_chart(fig, use_container_width=True)
112
+ if fig1 is not None:
113
+ st.plotly_chart(fig1, use_container_width=True)
114
+ if fig2 is not None:
115
+ st.plotly_chart(fig2, use_container_width=True)
116
+
117
+ # Extract data for AI analysis
118
+ portfolio_data = {
119
+ 'return': fig2.data[0].y[-1],
120
+ 'volatility': fig2.data[2].x[0],
121
+ 'sharpe': fig2.data[2].marker.color[0]
122
+ }
123
+ benchmark_data = {
124
+ 'return': fig2.data[1].y[-1],
125
+ 'volatility': fig2.data[2].x[1],
126
+ 'sharpe': fig2.data[2].marker.color[1]
127
+ }
128
+
129
+ # Signature
130
+ st.markdown("---")
131
+ st.markdown("""
132
+ <div style="text-align: center; color: #8d8d8d; font-size: 14px;">
133
+ Created by Luis Fernando Torres, 2024<br>
134
+ <a href="https://www.linkedin.com/in/luuisotorres/" target="_blank">LinkedIn</a> β€’
135
+ <a href="https://medium.com/@luuisotorres" target="_blank">Medium</a> β€’
136
+ <a href="https://www.kaggle.com/lusfernandotorres" target="_blank">Kaggle</a><br>
137
+ <a href="https://www.buymeacoffee.com/luuisotorres" target="_blank">Buy Me a Coffee β˜•</a><br>
138
+ <a href="https://luuisotorres.github.io/" target="_blank">https://luuisotorres.github.io/</a>
139
+ </div>
140
+ """, unsafe_allow_html=True)