-
Notifications
You must be signed in to change notification settings - Fork 11
/
dash_rss2.0.py
510 lines (415 loc) · 19.4 KB
/
dash_rss2.0.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
import streamlit as st
import yfinance as yf
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import pandas as pd
import feedparser
from fredapi import Fred
import base64
# Function to load the image and convert it to base64
def get_base64_of_bin_file(bin_file):
with open(bin_file, 'rb') as f:
data = f.read()
return base64.b64encode(data).decode()
# Path to the locally stored QR code image
qr_code_path = "qrcode.png" # Ensure the image is in your app directory
# Convert image to base64
qr_code_base64 = get_base64_of_bin_file(qr_code_path)
# Custom CSS to position the QR code close to the top-right corner under the "Deploy" area
st.markdown(
f"""
<style>
.qr-code {{
position: fixed; /* Keeps the QR code fixed in the viewport */
top: 10px; /* Sets the distance from the top of the viewport */
right: 10px; /* Sets the distance from the right of the viewport */
width: 200px; /* Adjusts the width of the QR code */
z-index: 100; /* Ensures the QR code stays above other elements */
}}
</style>
<img src="data:image/png;base64,{qr_code_base64}" class="qr-code">
""",
unsafe_allow_html=True
)
# Add FRED API configuration
try:
with open('fred.txt') as f:
FRED_API_KEY = f.read().strip()
fred = Fred(api_key=FRED_API_KEY)
except FileNotFoundError:
st.warning("fred.txt file not found. Risk-free rate functionality will be disabled.")
fred = None
except Exception as e:
st.warning(f"Error initializing FRED API: {str(e)}")
fred = None
@st.cache_data
def get_risk_free_rate():
"""Fetch the current risk-free rate (10-year Treasury yield) from FRED"""
if not fred:
return None
try:
ten_year_yield = fred.get_series('DGS10')
return ten_year_yield.tail(1).values[0] / 100 # Convert percentage to decimal
except Exception as e:
st.warning(f"Unable to fetch risk-free rate: {str(e)}")
return None
@st.cache_data
def get_market_return():
"""Calculate average annual market return for S&P 500 over the last 10 years"""
sp500 = yf.Ticker("^GSPC")
history = sp500.history(period="10y")
# Resample the data to get annual 'Close' values at year-end
annual_data = history['Close'].resample('Y').last()
# Calculate annual returns
annual_returns = annual_data.pct_change().dropna() * 100 # Convert to percentage
# Calculate the average annual market return
market_return = annual_returns.mean() / 100 # Convert to decimal
return market_return
@st.cache_data
def load_data(ticker):
data = yf.download(ticker)
return data
def calculate_sharpe_ratio(data, risk_free_rate, window=252):
"""Calculate rolling Sharpe ratio"""
if risk_free_rate is None:
return None
# Calculate daily returns
daily_returns = data['Close'].pct_change()
# Calculate excess returns over risk-free rate
excess_returns = daily_returns - (risk_free_rate / 252) # Daily risk-free rate
# Calculate rolling metrics
rolling_return = excess_returns.rolling(window=window).mean() * 252 # Annualized return
rolling_std = daily_returns.rolling(window=window).std() * (252 ** 0.5) # Annualized volatility
# Calculate Sharpe ratio
sharpe_ratio = rolling_return / rolling_std
return sharpe_ratio
def add_ema(data, periods):
for period in periods:
data[f'EMA_{period}'] = data['Close'].ewm(span=period, adjust=False).mean()
return data
def add_rsi(data, window=14):
delta = data['Close'].diff(1)
gain = delta.where(delta > 0, 0)
loss = -delta.where(delta < 0, 0)
avg_gain = gain.rolling(window=window).mean()
avg_loss = loss.rolling(window=window).mean()
rs = avg_gain / avg_loss
data['RSI'] = 100 - (100 / (1 + rs))
return data
def add_macd(data):
short_ema = data['Close'].ewm(span=12, adjust=False).mean()
long_ema = data['Close'].ewm(span=26, adjust=False).mean()
data['MACD'] = short_ema - long_ema
data['Signal Line'] = data['MACD'].ewm(span=9, adjust=False).mean()
return data
@st.cache_data
def get_fundamental_metrics(ticker):
stock = yf.Ticker(ticker)
info = stock.info
# Get risk-free rate
risk_free_rate = get_risk_free_rate()
# Get market return
market_return = get_market_return()
# Fetch balance sheet and financials data
balance_sheet = stock.balance_sheet
financials = stock.financials
# Get interest expense (from income statement) and total debt (from balance sheet)
interest_expense = financials.loc['Interest Expense'].iloc[0] if 'Interest Expense' in financials.index else 0
long_term_debt = balance_sheet.loc['Long Term Debt'].iloc[0] if 'Long Term Debt' in balance_sheet.index else 0
short_term_debt = balance_sheet.loc['Short Term Debt'].iloc[0] if 'Short Term Debt' in balance_sheet.index else 0
total_debt = long_term_debt + short_term_debt
# Get income statement to calculate tax rate using Tax Provision and Pretax Income
tax_provision = financials.loc['Tax Provision'].iloc[0] if 'Tax Provision' in financials.index else 0
pretax_income = financials.loc['Pretax Income'].iloc[0] if 'Pretax Income' in financials.index else 1 # Avoid division by zero
# Calculate the effective tax rate
tax_rate = tax_provision / pretax_income if pretax_income != 0 else 0
# Calculate cost of debt (adjusted for taxes)
cost_of_debt = (interest_expense / total_debt) * (1 - tax_rate) if total_debt != 0 else 0
# Get market capitalization (market value of equity)
market_cap = info.get('marketCap', None)
# Calculate cost of equity using CAPM
beta = info.get('beta', None) # Beta from Yahoo Finance
if beta is None:
raise ValueError("Beta value not found. Please check the ticker information.")
cost_of_equity = risk_free_rate + beta * (market_return - risk_free_rate)
# Calculate WACC
V = market_cap + total_debt # Total value (equity + debt)
WACC = (market_cap / V) * cost_of_equity + (total_debt / V) * cost_of_debt * (1 - tax_rate)
metrics = {
'Risk-Free Rate': f"{risk_free_rate:.2%}" if risk_free_rate is not None else 'N/A',
'Market Return': f"{market_return:.2%}" if market_return is not None else 'N/A',
'P/E Ratio': info.get('trailingPE', 'N/A'),
'ROE': info.get('returnOnEquity', 'N/A'),
'ROA': info.get('returnOnAssets', 'N/A'),
'Gross Margin': info.get('grossMargins', 'N/A'),
'Profit Margin': info.get('profitMargins', 'N/A'),
'Debt to Equity': info.get('debtToEquity', 'N/A'),
'Current Ratio': info.get('currentRatio', 'N/A'),
'Price to Book': info.get('priceToBook', 'N/A'),
'Earnings Per Share': info.get('trailingEps', 'N/A'),
'Dividend Yield': info.get('dividendYield', 'N/A'),
'Tax Rate': f"{tax_rate:.2%}", # New metric for tax rate
'Cost of Debt': f"{cost_of_debt:.2%}", # New metric for cost of debt
'WACC': f"{WACC:.2%}", # New metric for WACC
}
# Clean up metrics for display
for key, value in metrics.items():
if key in ['Risk-Free Rate', 'Market Return', 'Tax Rate', 'Cost of Debt', 'WACC']:
continue # Skip processing for these as they're already formatted
if isinstance(value, (int, float)):
metrics[key] = round(value, 2)
elif value == 'N/A':
metrics[key] = 'N/A'
else:
try:
metrics[key] = round(float(value), 2)
except ValueError:
metrics[key] = 'N/A'
return metrics
stock = yf.Ticker(ticker)
info = stock.info
# Get risk-free rate
risk_free_rate = get_risk_free_rate()
# Get market return
market_return = get_market_return()
# Fetch balance sheet and financials data
balance_sheet = stock.balance_sheet
financials = stock.financials
# Get interest expense (from income statement) and total debt (from balance sheet)
interest_expense = financials.loc['Interest Expense'].iloc[0] if 'Interest Expense' in financials.index else 0
total_debt = balance_sheet.loc['Total Debt'].iloc[0] if 'Total Debt' in balance_sheet.index else 0
# Get income statement to calculate tax rate using Tax Provision and Pretax Income
tax_provision = financials.loc['Tax Provision'].iloc[0] if 'Tax Provision' in financials.index else 0
pretax_income = financials.loc['Pretax Income'].iloc[0] if 'Pretax Income' in financials.index else 1 # Avoid division by zero
# Calculate the effective tax rate
tax_rate = tax_provision / pretax_income if pretax_income != 0 else 0
# Calculate cost of debt (adjusted for taxes)
cost_of_debt = (interest_expense / total_debt) * (1 - tax_rate) if total_debt != 0 else 0
metrics = {
'Risk-Free Rate': f"{risk_free_rate:.2%}" if risk_free_rate is not None else 'N/A',
'Market Return': f"{market_return:.2%}" if market_return is not None else 'N/A',
'P/E Ratio': info.get('trailingPE', 'N/A'),
'ROE': info.get('returnOnEquity', 'N/A'),
'ROA': info.get('returnOnAssets', 'N/A'),
'Gross Margin': info.get('grossMargins', 'N/A'),
'Profit Margin': info.get('profitMargins', 'N/A'),
'Debt to Equity': info.get('debtToEquity', 'N/A'),
'Current Ratio': info.get('currentRatio', 'N/A'),
'Price to Book': info.get('priceToBook', 'N/A'),
'Earnings Per Share': info.get('trailingEps', 'N/A'),
'Dividend Yield': info.get('dividendYield', 'N/A'),
'Tax Rate': f"{tax_rate:.2%}", # New metric for tax rate
'Cost of Debt': f"{cost_of_debt:.2%}", # New metric for cost of debt
}
# Clean up metrics for display
for key, value in metrics.items():
if key in ['Risk-Free Rate', 'Market Return', 'Tax Rate', 'Cost of Debt']:
continue # Skip processing for these as they're already formatted
if isinstance(value, (int, float)):
metrics[key] = round(value, 2)
elif value == 'N/A':
metrics[key] = 'N/A'
else:
try:
metrics[key] = round(float(value), 2)
except ValueError:
metrics[key] = 'N/A'
return metrics
stock = yf.Ticker(ticker)
info = stock.info
# Get risk-free rate
risk_free_rate = get_risk_free_rate()
# Get market return
market_return = get_market_return()
metrics = {
'Risk-Free Rate': f"{risk_free_rate:.2%}" if risk_free_rate is not None else 'N/A',
'Market Return': f"{market_return:.2%}" if market_return is not None else 'N/A',
'P/E Ratio': info.get('trailingPE', 'N/A'),
'ROE': info.get('returnOnEquity', 'N/A'),
'ROA': info.get('returnOnAssets', 'N/A'),
'Gross Margin': info.get('grossMargins', 'N/A'),
'Profit Margin': info.get('profitMargins', 'N/A'),
'Debt to Equity': info.get('debtToEquity', 'N/A'),
'Current Ratio': info.get('currentRatio', 'N/A'),
'Price to Book': info.get('priceToBook', 'N/A'),
'Earnings Per Share': info.get('trailingEps', 'N/A'),
'Dividend Yield': info.get('dividendYield', 'N/A'),
}
for key, value in metrics.items():
if key in ['Risk-Free Rate', 'Market Return']:
continue # Skip processing for these as they're already formatted
if isinstance(value, (int, float)):
metrics[key] = round(value, 2)
elif value == 'N/A':
metrics[key] = 'N/A'
else:
try:
metrics[key] = round(float(value), 2)
except ValueError:
metrics[key] = 'N/A'
return metrics
@st.cache_data
def fetch_rss_feed(ticker):
feed_url = f"https://finance.yahoo.com/rss/headline?s={ticker}"
feed = feedparser.parse(feed_url)
return feed
# Main app
st.title('Interactive Stock Chart with Technical Indicators and Fundamental Metrics')
# Sidebar for user inputs and news feed
st.sidebar.title('Stock Ticker and News')
ticker = st.sidebar.text_input('Enter Stock Ticker', 'GOOGL').upper()
# Load stock data
data = load_data(ticker)
# Get risk-free rate for Sharpe ratio calculation
risk_free_rate = get_risk_free_rate()
# Time period selection
periods = st.slider('Select Time Period (in days)', 30, 365, 180)
# EMA selection
selected_emas = st.multiselect('Select EMA periods', [200, 50, 20], default=[200, 50, 20])
# Indicator plots selection
add_rsi_plot = st.checkbox('Add RSI Subplot')
add_macd_plot = st.checkbox('Add MACD Subplot')
# Calculate Sharpe ratio if risk-free rate is available
if risk_free_rate is not None:
data['Sharpe Ratio'] = calculate_sharpe_ratio(data, risk_free_rate)
add_sharpe = st.checkbox('Add Sharpe Ratio Subplot')
st.subheader('Select Fundamental Metrics to Display')
metrics = get_fundamental_metrics(ticker)
default_metrics = ['Risk-Free Rate', 'Market Return', 'P/E Ratio', 'ROE', 'Profit Margin']
selected_metrics = st.multiselect('Choose metrics', list(metrics.keys()), default=default_metrics)
if selected_metrics:
st.subheader('Fundamental Metrics')
for i in range(0, len(selected_metrics), 3):
cols = st.columns(3)
for j in range(3):
if i + j < len(selected_metrics):
metric = selected_metrics[i + j]
cols[j].metric(label=metric, value=metrics[metric])
# Add selected EMAs to data
data = add_ema(data, selected_emas)
# Slice data for the selected period
data_period = data[-periods:]
# Add indicators if selected
if add_rsi_plot:
data_period = add_rsi(data_period)
if add_macd_plot:
data_period = add_macd(data_period)
# Previous imports and functions remain the same...
# Function to calculate the price range
def calculate_price_range(data):
if data.empty:
return [0, 1]
# Convert Series min/max to float values
low_min = float(data['Low'].min())
close_min = float(data['Close'].min())
high_max = float(data['High'].max())
close_max = float(data['Close'].max())
# Calculate bounds using float values
lower_bound = min(low_min, close_min) * 0.95
upper_bound = max(high_max, close_max) * 1.05
return [lower_bound, upper_bound]
# Generic function to calculate range for any specified column
def calculate_custom_range(data, column_name):
if data.empty:
return [0, 1]
# Calculate min and max for the specified column
column_min = float(data[column_name].min())
column_max = float(data[column_name].max())
# Calculate bounds with a buffer
lower_bound = column_min * 0.95
upper_bound = column_max * 1.05
return [lower_bound, upper_bound]
# Calculate ranges for various metrics
price_range = calculate_price_range(data_period) # For price-specific range
rsi_range = [0, 100] # RSI is always 0-100
# Calculate Sharpe Ratio range using the generic function
sharpe_range = calculate_custom_range(data_period, 'Sharpe Ratio')
# Update MACD range calculation
if add_macd_plot:
macd_min = float(data_period['MACD'].min())
signal_min = float(data_period['Signal Line'].min())
macd_max = float(data_period['MACD'].max())
signal_max = float(data_period['Signal Line'].max())
macd_range = [
min(macd_min, signal_min) * 1.05,
max(macd_max, signal_max) * 1.05
]
else:
macd_range = [0, 0]
# Rest of the plotting code remains the same...
# Create subplots
subplot_titles = ['Price']
if add_rsi_plot:
subplot_titles.append('RSI')
if add_macd_plot:
subplot_titles.append('MACD')
if risk_free_rate is not None and add_sharpe:
subplot_titles.append('Sharpe Ratio')
rows = 1 + add_rsi_plot + add_macd_plot + (add_sharpe if risk_free_rate is not None else 0)
fig = make_subplots(rows=rows, cols=1, shared_xaxes=True,
vertical_spacing=0.15,
row_heights=[0.5] + [0.25] * (rows - 1),
subplot_titles=subplot_titles)
# Add candlestick chart
if not data_period.empty:
fig.add_trace(go.Candlestick(x=data_period.index,
open=data_period['Open'],
high=data_period['High'],
low=data_period['Low'],
close=data_period['Close'],
name='Candlesticks'), row=1, col=1)
# Add selected EMA traces
for period in selected_emas:
if f'EMA_{period}' in data_period.columns:
fig.add_trace(go.Scatter(x=data_period.index,
y=data_period[f'EMA_{period}'],
mode='lines',
name=f'EMA {period}'), row=1, col=1)
# Update price axis with safety check
if not pd.isna(price_range[0]) and not pd.isna(price_range[1]):
fig.update_yaxes(title_text='Price', row=1, col=1, range=price_range)
else:
fig.update_yaxes(title_text='Price', row=1, col=1)
# Initialize current_row
current_row = 2
# Add RSI trace
if add_rsi_plot and not data_period.empty and 'RSI' in data_period.columns:
fig.add_trace(go.Scatter(x=data_period.index,
y=data_period['RSI'],
mode='lines',
name='RSI'), row=current_row, col=1)
fig.add_hline(y=70, line=dict(color='red', dash='dash'), row=current_row, col=1)
fig.add_hline(y=30, line=dict(color='green', dash='dash'), row=current_row, col=1)
fig.update_yaxes(title_text='RSI', range=rsi_range, row=current_row, col=1)
current_row += 1
# Add MACD traces
if add_macd_plot and not data_period.empty and 'MACD' in data_period.columns:
fig.add_trace(go.Scatter(x=data_period.index,
y=data_period['MACD'],
mode='lines',
name='MACD'), row=current_row, col=1)
fig.add_trace(go.Scatter(x=data_period.index,
y=data_period['Signal Line'],
mode='lines',
name='Signal Line'), row=current_row, col=1)
fig.update_yaxes(title_text='MACD', row=current_row, col=1, range=macd_range)
current_row += 1
# Add Sharpe ratio trace
if risk_free_rate is not None and add_sharpe and 'Sharpe Ratio' in data_period.columns:
sharpe_range = calculate_custom_range(data_period, 'Sharpe Ratio')
fig.add_trace(go.Scatter(x=data_period.index,
y=data_period['Sharpe Ratio'],
mode='lines',
name='Sharpe Ratio'), row=current_row, col=1)
fig.update_yaxes(title_text='Sharpe Ratio', row=current_row, col=1, range=sharpe_range)
# Final layout adjustments
fig.update_layout(height=800,
title=f"{ticker} Stock Price with Indicators",
xaxis_rangeslider_visible=False)
# Display the plot
st.plotly_chart(fig)
# Sidebar for news feed
st.sidebar.title(f"{ticker} News Feed")
feed = fetch_rss_feed(ticker)
for entry in feed.entries[:10]:
st.sidebar.write(f"[{entry.title}]({entry.link})")