QuantumLearner commited on
Commit
e2cea3a
·
verified ·
1 Parent(s): db7a7f0

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +347 -0
app.py ADDED
@@ -0,0 +1,347 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import requests
3
+ import pandas as pd
4
+ import plotly.express as px
5
+ import plotly.io as pio
6
+ import os
7
+
8
+ # Update the default color sequence for the plotly_dark template to green.
9
+ pio.templates["plotly_dark"].layout.colorway = ["#00cc44"]
10
+
11
+ # Global API key and constant limit (backend only)
12
+ API_KEY = os.getenv("FMP_API_KEY")
13
+ BASE_URL = "https://financialmodelingprep.com/api/v3/stock-screener"
14
+ LIMIT = 1000
15
+
16
+ # Set wide layout and page title
17
+ st.set_page_config(layout="wide", page_title="Stock Screener")
18
+
19
+ # App explanation in main area
20
+ st.title("Stock Screener")
21
+
22
+ st.write(
23
+ "A research tool for filtering stocks by price, volume, market cap, sector, and other fundamentals. "
24
+ "Useful for identifying sets of securities based on specific criteria."
25
+ )
26
+
27
+ st.info(
28
+ "Use the sidebar to set filters. Click **Run Analysis** to retrieve results and display charts. "
29
+ "All filters are optional. If no results appear, try loosening the constraints."
30
+ )
31
+
32
+ # Sidebar inputs
33
+ st.sidebar.header("Filters")
34
+
35
+ # Numeric Filters
36
+ with st.sidebar.expander("Numeric Filters", expanded=True):
37
+ market_cap_min = st.number_input(
38
+ "Market Cap More Than",
39
+ value=1000000000,
40
+ help="Minimum market capitalization."
41
+ )
42
+ market_cap_max = st.number_input(
43
+ "Market Cap Lower Than",
44
+ value=50000000000,
45
+ help="Maximum market capitalization."
46
+ )
47
+ price_min = st.number_input(
48
+ "Price More Than",
49
+ value=10.0,
50
+ help="Minimum stock price."
51
+ )
52
+ price_max = st.number_input(
53
+ "Price Lower Than",
54
+ value=500.0,
55
+ help="Maximum stock price."
56
+ )
57
+ beta_min = st.number_input(
58
+ "Beta More Than",
59
+ value=0.5,
60
+ help="Minimum beta value."
61
+ )
62
+ beta_max = st.number_input(
63
+ "Beta Lower Than",
64
+ value=2.0,
65
+ help="Maximum beta value."
66
+ )
67
+ volume_min = st.number_input(
68
+ "Volume More Than",
69
+ value=10000,
70
+ help="Minimum trading volume."
71
+ )
72
+ volume_max = st.number_input(
73
+ "Volume Lower Than",
74
+ value=1000000,
75
+ help="Maximum trading volume."
76
+ )
77
+ dividend_min = st.number_input(
78
+ "Dividend More Than",
79
+ value=0.0,
80
+ help="Minimum dividend value."
81
+ )
82
+ dividend_max = st.number_input(
83
+ "Dividend Lower Than",
84
+ value=5.0,
85
+ help="Maximum dividend value."
86
+ )
87
+
88
+ # Categorical Filters
89
+ with st.sidebar.expander("Categorical Filters", expanded=True):
90
+ sector_options = [
91
+ "Technology", "Financial Services", "Consumer Cyclical", "Energy", "Industrials",
92
+ "Basic Materials", "Communication Services", "Consumer Defensive", "Healthcare",
93
+ "Real Estate", "Utilities", "Industrial Goods", "Financial", "Services", "Conglomerates"
94
+ ]
95
+ selected_sectors = st.multiselect(
96
+ "Sector",
97
+ options=sector_options,
98
+ help="Select one or more sectors."
99
+ )
100
+ industry_options = [
101
+ "Autos", "Banks", "Banks Diversified", "Software", "Banks Regional",
102
+ "Beverages Alcoholic", "Beverages Brewers", "Beverages Non-Alcoholic"
103
+ ]
104
+ selected_industries = st.multiselect(
105
+ "Industry",
106
+ options=industry_options,
107
+ help="Select one or more industries."
108
+ )
109
+ country_options = ["US", "UK", "MX", "BR", "RU", "HK", "CA"]
110
+ selected_countries = st.multiselect(
111
+ "Country",
112
+ options=country_options,
113
+ help="Select one or more countries."
114
+ )
115
+ exchange_options = ["nyse", "nasdaq", "amex", "euronext", "tsx", "etf", "mutual_fund"]
116
+ selected_exchanges = st.multiselect(
117
+ "Exchange",
118
+ options=exchange_options,
119
+ help="Select one or more exchanges."
120
+ )
121
+
122
+ # Boolean Filters
123
+ with st.sidebar.expander("Boolean Filters", expanded=True):
124
+ is_etf = st.checkbox(
125
+ "Is ETF",
126
+ value=False,
127
+ help="Check to return only ETFs."
128
+ )
129
+ is_fund = st.checkbox(
130
+ "Is Fund",
131
+ value=False,
132
+ help="Check to return only funds."
133
+ )
134
+ is_actively_trading = st.checkbox(
135
+ "Is Actively Trading",
136
+ value=True,
137
+ help="Check to return only actively traded stocks."
138
+ )
139
+
140
+ # Run Analysis button (placed outside expanders)
141
+ run_analysis = st.sidebar.button("Run Analysis")
142
+
143
+ def get_stock_data(params):
144
+ # Copy parameters and add API key.
145
+ filters = params.copy()
146
+ filters["apikey"] = API_KEY
147
+ # Convert list values to comma-separated strings.
148
+ for key, value in filters.items():
149
+ if isinstance(value, list) and value:
150
+ filters[key] = ",".join(map(str, value))
151
+ try:
152
+ response = requests.get(BASE_URL, params=filters, timeout=10)
153
+ response.raise_for_status()
154
+ data = response.json()
155
+ if not data:
156
+ st.error("No results found for the provided filters.")
157
+ return pd.DataFrame()
158
+ return pd.DataFrame(data)
159
+ except Exception:
160
+ st.error("An error occurred while fetching data.")
161
+ return pd.DataFrame()
162
+
163
+ if run_analysis:
164
+ # Build parameter dictionary.
165
+ params = {}
166
+ params["marketCapMoreThan"] = market_cap_min
167
+ params["marketCapLowerThan"] = market_cap_max
168
+ params["priceMoreThan"] = price_min
169
+ params["priceLowerThan"] = price_max
170
+ params["betaMoreThan"] = beta_min
171
+ params["betaLowerThan"] = beta_max
172
+ params["volumeMoreThan"] = volume_min
173
+ params["volumeLowerThan"] = volume_max
174
+ params["dividendMoreThan"] = dividend_min
175
+ params["dividendLowerThan"] = dividend_max
176
+
177
+ if selected_sectors:
178
+ params["sector"] = selected_sectors
179
+ if selected_industries:
180
+ params["industry"] = selected_industries
181
+ if selected_countries:
182
+ params["country"] = selected_countries
183
+ if selected_exchanges:
184
+ params["exchange"] = selected_exchanges
185
+
186
+ params["isEtf"] = is_etf
187
+ params["isFund"] = is_fund
188
+ params["isActivelyTrading"] = is_actively_trading
189
+
190
+ # Set limit in the backend.
191
+ params["limit"] = LIMIT
192
+
193
+ with st.spinner("Fetching stock data..."):
194
+ df = get_stock_data(params)
195
+
196
+ if not df.empty:
197
+ st.success("Data fetched successfully!")
198
+
199
+ #with st.expander("Results", expanded=True):
200
+ with st.container(border=True):
201
+ # Display the data table.
202
+ with st.container(border=True):
203
+ st.dataframe(df)
204
+
205
+ # Common hover data for scatter plots.
206
+ hover_fields = ["symbol", "companyName", "sector", "industry", "exchangeShortName", "country"]
207
+
208
+ # Chart 1: Price vs Market Cap (Scatter)
209
+ with st.container(border=True):
210
+ try:
211
+ fig1 = px.scatter(
212
+ df,
213
+ x="price",
214
+ y="marketCap",
215
+ size="volume",
216
+ title="Price vs Market Cap",
217
+ hover_data=hover_fields,
218
+ template="plotly_dark"
219
+ )
220
+ st.plotly_chart(fig1, use_container_width=True)
221
+ except Exception:
222
+ st.warning("Scatter plot could not be generated.")
223
+
224
+ # Chart 2: Sector Distribution (Bar Chart)
225
+ with st.container(border=True):
226
+ try:
227
+ sector_counts = df["sector"].value_counts().reset_index()
228
+ sector_counts.columns = ["Sector", "Count"]
229
+ fig2 = px.bar(
230
+ sector_counts,
231
+ x="Sector",
232
+ y="Count",
233
+ title="Sector Distribution",
234
+ template="plotly_dark"
235
+ )
236
+ st.plotly_chart(fig2, use_container_width=True)
237
+ except Exception:
238
+ st.warning("Sector distribution chart could not be generated.")
239
+
240
+ # Chart 3: Price Distribution (Histogram)
241
+ with st.container(border=True):
242
+ try:
243
+ fig3 = px.histogram(
244
+ df,
245
+ x="price",
246
+ nbins=30,
247
+ title="Price Distribution",
248
+ template="plotly_dark"
249
+ )
250
+ st.plotly_chart(fig3, use_container_width=True)
251
+ except Exception:
252
+ st.warning("Price distribution chart could not be generated.")
253
+
254
+ # Chart 4: Market Cap vs Volume (Scatter)
255
+ with st.container(border=True):
256
+ try:
257
+ fig4 = px.scatter(
258
+ df,
259
+ x="volume",
260
+ y="marketCap",
261
+ size="price",
262
+ title="Market Cap vs Volume",
263
+ hover_data=hover_fields,
264
+ template="plotly_dark"
265
+ )
266
+ st.plotly_chart(fig4, use_container_width=True)
267
+ except Exception:
268
+ st.warning("Market Cap vs Volume chart could not be generated.")
269
+
270
+ # Chart 5: Country Breakdown (Bar Chart)
271
+ with st.container(border=True):
272
+ try:
273
+ country_counts = df["country"].value_counts().reset_index()
274
+ country_counts.columns = ["Country", "Count"]
275
+ fig5 = px.bar(
276
+ country_counts,
277
+ x="Country",
278
+ y="Count",
279
+ title="Country Breakdown",
280
+ template="plotly_dark",
281
+ #color_discrete_sequence=px.colors.qualitative.Plotly # override global green
282
+ )
283
+ st.plotly_chart(fig5, use_container_width=True)
284
+ except Exception:
285
+ st.warning("Country breakdown chart could not be generated.")
286
+
287
+ # Chart 6: Dividend vs Price (Scatter)
288
+ with st.container(border=True):
289
+ try:
290
+ if "lastAnnualDividend" in df.columns:
291
+ fig6 = px.scatter(
292
+ df,
293
+ x="price",
294
+ y="lastAnnualDividend",
295
+ title="Dividend vs Price",
296
+ hover_data=hover_fields,
297
+ template="plotly_dark"
298
+ )
299
+ st.plotly_chart(fig6, use_container_width=True)
300
+ else:
301
+ st.info("Dividend data is not available.")
302
+ except Exception:
303
+ st.warning("Dividend vs Price chart could not be generated.")
304
+
305
+ # Chart 7: Beta Distribution (Histogram)
306
+ with st.container(border=True):
307
+ try:
308
+ fig7 = px.histogram(
309
+ df,
310
+ x="beta",
311
+ nbins=30,
312
+ title="Beta Distribution",
313
+ template="plotly_dark"
314
+ )
315
+ st.plotly_chart(fig7, use_container_width=True)
316
+ except Exception:
317
+ st.warning("Beta distribution chart could not be generated.")
318
+
319
+ # Chart 8: Exchange Breakdown (Bar Chart)
320
+ with st.container(border=True):
321
+ try:
322
+ if "exchangeShortName" in df.columns:
323
+ exchange_counts = df["exchangeShortName"].value_counts().reset_index()
324
+ exchange_counts.columns = ["Exchange", "Count"]
325
+ fig8 = px.bar(
326
+ exchange_counts,
327
+ x="Exchange",
328
+ y="Count",
329
+ title="Exchange Breakdown",
330
+ template="plotly_dark"
331
+ )
332
+ st.plotly_chart(fig8, use_container_width=True)
333
+ else:
334
+ st.info("Exchange data is not available.")
335
+ except Exception:
336
+ st.warning("Exchange breakdown chart could not be generated.")
337
+
338
+ # Hide default Streamlit style
339
+ st.markdown(
340
+ """
341
+ <style>
342
+ #MainMenu {visibility: hidden;}
343
+ footer {visibility: hidden;}
344
+ </style>
345
+ """,
346
+ unsafe_allow_html=True
347
+ )