File size: 12,406 Bytes
e2cea3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import streamlit as st
import requests
import pandas as pd
import plotly.express as px
import plotly.io as pio
import os

# Update the default color sequence for the plotly_dark template to green.
pio.templates["plotly_dark"].layout.colorway = ["#00cc44"]

# Global API key and constant limit (backend only)
API_KEY = os.getenv("FMP_API_KEY")
BASE_URL = "https://financialmodelingprep.com/api/v3/stock-screener"
LIMIT = 1000

# Set wide layout and page title
st.set_page_config(layout="wide", page_title="Stock Screener")

# App explanation in main area
st.title("Stock Screener")

st.write(
    "A research tool for filtering stocks by price, volume, market cap, sector, and other fundamentals. "
    "Useful for identifying sets of securities based on specific criteria."
)

st.info(
    "Use the sidebar to set filters. Click **Run Analysis** to retrieve results and display charts. "
    "All filters are optional. If no results appear, try loosening the constraints."
)

# Sidebar inputs
st.sidebar.header("Filters")

# Numeric Filters
with st.sidebar.expander("Numeric Filters", expanded=True):
    market_cap_min = st.number_input(
        "Market Cap More Than",
        value=1000000000,
        help="Minimum market capitalization."
    )
    market_cap_max = st.number_input(
        "Market Cap Lower Than",
        value=50000000000,
        help="Maximum market capitalization."
    )
    price_min = st.number_input(
        "Price More Than",
        value=10.0,
        help="Minimum stock price."
    )
    price_max = st.number_input(
        "Price Lower Than",
        value=500.0,
        help="Maximum stock price."
    )
    beta_min = st.number_input(
        "Beta More Than",
        value=0.5,
        help="Minimum beta value."
    )
    beta_max = st.number_input(
        "Beta Lower Than",
        value=2.0,
        help="Maximum beta value."
    )
    volume_min = st.number_input(
        "Volume More Than",
        value=10000,
        help="Minimum trading volume."
    )
    volume_max = st.number_input(
        "Volume Lower Than",
        value=1000000,
        help="Maximum trading volume."
    )
    dividend_min = st.number_input(
        "Dividend More Than",
        value=0.0,
        help="Minimum dividend value."
    )
    dividend_max = st.number_input(
        "Dividend Lower Than",
        value=5.0,
        help="Maximum dividend value."
    )

# Categorical Filters
with st.sidebar.expander("Categorical Filters", expanded=True):
    sector_options = [
        "Technology", "Financial Services", "Consumer Cyclical", "Energy", "Industrials",
        "Basic Materials", "Communication Services", "Consumer Defensive", "Healthcare",
        "Real Estate", "Utilities", "Industrial Goods", "Financial", "Services", "Conglomerates"
    ]
    selected_sectors = st.multiselect(
        "Sector",
        options=sector_options,
        help="Select one or more sectors."
    )
    industry_options = [
        "Autos", "Banks", "Banks Diversified", "Software", "Banks Regional",
        "Beverages Alcoholic", "Beverages Brewers", "Beverages Non-Alcoholic"
    ]
    selected_industries = st.multiselect(
        "Industry",
        options=industry_options,
        help="Select one or more industries."
    )
    country_options = ["US", "UK", "MX", "BR", "RU", "HK", "CA"]
    selected_countries = st.multiselect(
        "Country",
        options=country_options,
        help="Select one or more countries."
    )
    exchange_options = ["nyse", "nasdaq", "amex", "euronext", "tsx", "etf", "mutual_fund"]
    selected_exchanges = st.multiselect(
        "Exchange",
        options=exchange_options,
        help="Select one or more exchanges."
    )

# Boolean Filters
with st.sidebar.expander("Boolean Filters", expanded=True):
    is_etf = st.checkbox(
        "Is ETF",
        value=False,
        help="Check to return only ETFs."
    )
    is_fund = st.checkbox(
        "Is Fund",
        value=False,
        help="Check to return only funds."
    )
    is_actively_trading = st.checkbox(
        "Is Actively Trading",
        value=True,
        help="Check to return only actively traded stocks."
    )

# Run Analysis button (placed outside expanders)
run_analysis = st.sidebar.button("Run Analysis")

def get_stock_data(params):
    # Copy parameters and add API key.
    filters = params.copy()
    filters["apikey"] = API_KEY
    # Convert list values to comma-separated strings.
    for key, value in filters.items():
        if isinstance(value, list) and value:
            filters[key] = ",".join(map(str, value))
    try:
        response = requests.get(BASE_URL, params=filters, timeout=10)
        response.raise_for_status()
        data = response.json()
        if not data:
            st.error("No results found for the provided filters.")
            return pd.DataFrame()
        return pd.DataFrame(data)
    except Exception:
        st.error("An error occurred while fetching data.")
        return pd.DataFrame()

if run_analysis:
    # Build parameter dictionary.
    params = {}
    params["marketCapMoreThan"] = market_cap_min
    params["marketCapLowerThan"] = market_cap_max
    params["priceMoreThan"] = price_min
    params["priceLowerThan"] = price_max
    params["betaMoreThan"] = beta_min
    params["betaLowerThan"] = beta_max
    params["volumeMoreThan"] = volume_min
    params["volumeLowerThan"] = volume_max
    params["dividendMoreThan"] = dividend_min
    params["dividendLowerThan"] = dividend_max

    if selected_sectors:
        params["sector"] = selected_sectors
    if selected_industries:
        params["industry"] = selected_industries
    if selected_countries:
        params["country"] = selected_countries
    if selected_exchanges:
        params["exchange"] = selected_exchanges

    params["isEtf"] = is_etf
    params["isFund"] = is_fund
    params["isActivelyTrading"] = is_actively_trading

    # Set limit in the backend.
    params["limit"] = LIMIT

    with st.spinner("Fetching stock data..."):
        df = get_stock_data(params)

    if not df.empty:
        st.success("Data fetched successfully!")
        
        #with st.expander("Results", expanded=True):
        with st.container(border=True):
            # Display the data table.
            with st.container(border=True):
                st.dataframe(df)

            # Common hover data for scatter plots.
            hover_fields = ["symbol", "companyName", "sector", "industry", "exchangeShortName", "country"]

            # Chart 1: Price vs Market Cap (Scatter)
            with st.container(border=True):
                try:
                    fig1 = px.scatter(
                        df,
                        x="price",
                        y="marketCap",
                        size="volume",
                        title="Price vs Market Cap",
                        hover_data=hover_fields,
                        template="plotly_dark"
                    )
                    st.plotly_chart(fig1, use_container_width=True)
                except Exception:
                    st.warning("Scatter plot could not be generated.")

            # Chart 2: Sector Distribution (Bar Chart)
            with st.container(border=True):
                try:
                    sector_counts = df["sector"].value_counts().reset_index()
                    sector_counts.columns = ["Sector", "Count"]
                    fig2 = px.bar(
                        sector_counts,
                        x="Sector",
                        y="Count",
                        title="Sector Distribution",
                        template="plotly_dark"
                    )
                    st.plotly_chart(fig2, use_container_width=True)
                except Exception:
                    st.warning("Sector distribution chart could not be generated.")

            # Chart 3: Price Distribution (Histogram)
            with st.container(border=True):
                try:
                    fig3 = px.histogram(
                        df,
                        x="price",
                        nbins=30,
                        title="Price Distribution",
                        template="plotly_dark"
                    )
                    st.plotly_chart(fig3, use_container_width=True)
                except Exception:
                    st.warning("Price distribution chart could not be generated.")

            # Chart 4: Market Cap vs Volume (Scatter)
            with st.container(border=True):
                try:
                    fig4 = px.scatter(
                        df,
                        x="volume",
                        y="marketCap",
                        size="price",
                        title="Market Cap vs Volume",
                        hover_data=hover_fields,
                        template="plotly_dark"
                    )
                    st.plotly_chart(fig4, use_container_width=True)
                except Exception:
                    st.warning("Market Cap vs Volume chart could not be generated.")

            # Chart 5: Country Breakdown (Bar Chart)
            with st.container(border=True):
                try:
                    country_counts = df["country"].value_counts().reset_index()
                    country_counts.columns = ["Country", "Count"]
                    fig5 = px.bar(
                        country_counts,
                        x="Country",
                        y="Count",
                        title="Country Breakdown",
                        template="plotly_dark",
                        #color_discrete_sequence=px.colors.qualitative.Plotly  # override global green
                    )
                    st.plotly_chart(fig5, use_container_width=True)
                except Exception:
                    st.warning("Country breakdown chart could not be generated.")

            # Chart 6: Dividend vs Price (Scatter)
            with st.container(border=True):
                try:
                    if "lastAnnualDividend" in df.columns:
                        fig6 = px.scatter(
                            df,
                            x="price",
                            y="lastAnnualDividend",
                            title="Dividend vs Price",
                            hover_data=hover_fields,
                            template="plotly_dark"
                        )
                        st.plotly_chart(fig6, use_container_width=True)
                    else:
                        st.info("Dividend data is not available.")
                except Exception:
                    st.warning("Dividend vs Price chart could not be generated.")

            # Chart 7: Beta Distribution (Histogram)
            with st.container(border=True):
                try:
                    fig7 = px.histogram(
                        df,
                        x="beta",
                        nbins=30,
                        title="Beta Distribution",
                        template="plotly_dark"
                    )
                    st.plotly_chart(fig7, use_container_width=True)
                except Exception:
                    st.warning("Beta distribution chart could not be generated.")

            # Chart 8: Exchange Breakdown (Bar Chart)
            with st.container(border=True):
                try:
                    if "exchangeShortName" in df.columns:
                        exchange_counts = df["exchangeShortName"].value_counts().reset_index()
                        exchange_counts.columns = ["Exchange", "Count"]
                        fig8 = px.bar(
                            exchange_counts,
                            x="Exchange",
                            y="Count",
                            title="Exchange Breakdown",
                            template="plotly_dark"
                        )
                        st.plotly_chart(fig8, use_container_width=True)
                    else:
                        st.info("Exchange data is not available.")
                except Exception:
                    st.warning("Exchange breakdown chart could not be generated.")
            
# Hide default Streamlit style
st.markdown(
    """
    <style>
    #MainMenu {visibility: hidden;}
    footer {visibility: hidden;}
    </style>
    """,
    unsafe_allow_html=True
)