QuantumLearner commited on
Commit
8588add
·
verified ·
1 Parent(s): 6df2639

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +46 -52
app.py CHANGED
@@ -7,7 +7,6 @@ import dateutil.relativedelta
7
  import os
8
 
9
  # ---- PAGE CONFIG ----
10
- # Makes the layout span the full width of the browser
11
  st.set_page_config(layout="wide")
12
 
13
  # ---- GLOBALS ----
@@ -17,24 +16,42 @@ API_KEY = os.getenv("FMP_API_KEY")
17
  st.sidebar.title("User Inputs")
18
 
19
  with st.sidebar.expander("Configuration", expanded=True):
20
- # Provide a tooltip for clarity
21
  ticker = st.text_input("Ticker:", "ASML", help="Insert the stock ticker.")
22
- # Let the user pick how many years of data to retrieve
23
- years_back = st.number_input(
24
- "Years of historical data:",
25
- min_value=1,
26
- max_value=50,
27
- value=15,
28
- help="Choose how many years of data to retrieve."
29
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
- # A key button that triggers the data fetching and analysis
32
  run_button = st.sidebar.button("Run Analysis")
33
 
34
-
35
  # ---- HELPER FUNCTION: VALUE FORMATTING ----
36
  def format_value(x):
37
- # Formats large numeric values for readability
38
  if abs(x) >= 1e9:
39
  return f"{x/1e9:.1f}B"
40
  elif abs(x) >= 1e6:
@@ -44,50 +61,43 @@ def format_value(x):
44
  else:
45
  return f"{x:.1f}"
46
 
47
-
48
  # ---- MAIN APP START ----
49
  def main():
50
  st.title("Analyst Forecasts & Estimates")
51
- st.write("This tool fetches historical financial data and analyst forecasts. It helps you see past trends and future estimates.")
52
-
 
53
  if not run_button:
54
- st.info("Set your preferred inputs on the sidebar, then click **Run Analysis**.")
55
  return
56
 
57
- # Validate if ticker is provided
58
  if not ticker.strip():
59
  st.error("Please enter a valid ticker.")
60
  return
61
 
62
  # ---- FETCH AND PREPARE DATA ----
63
- # Build the URLs using the global API_KEY
64
  hist_url = (
65
  f"https://financialmodelingprep.com/api/v3/income-statement/{ticker}"
66
- f"?period=annual&limit={years_back}&apikey={API_KEY}"
67
  )
68
  forecast_url = (
69
  f"https://financialmodelingprep.com/api/v3/analyst-estimates/{ticker}"
70
- f"?apikey={API_KEY}"
71
  )
72
-
73
  try:
74
- # Attempt to request the data
75
  hist_data = requests.get(hist_url, timeout=10).json()
76
  forecast_data = requests.get(forecast_url, timeout=10).json()
77
  except Exception:
78
  st.error("Could not retrieve data at this time.")
79
  return
80
 
81
- # Convert raw JSON into DataFrames
82
  historical_df = pd.DataFrame(hist_data)
83
  forecast_df = pd.DataFrame(forecast_data)
84
 
85
- # Basic check if data is not empty
86
  if historical_df.empty and forecast_df.empty:
87
  st.warning("No data found for the specified ticker.")
88
  return
89
 
90
- # Parse dates
91
  if not historical_df.empty and "date" in historical_df.columns:
92
  historical_df["date"] = pd.to_datetime(historical_df["date"])
93
  historical_df.sort_values("date", inplace=True)
@@ -95,16 +105,12 @@ def main():
95
  forecast_df["date"] = pd.to_datetime(forecast_df["date"])
96
  forecast_df.sort_values("date", inplace=True)
97
 
98
- # Define a cutoff based on the number of years
99
- cutoff_date = datetime.now() - dateutil.relativedelta.relativedelta(years=years_back)
100
-
101
- # Filter the data within that range
102
  if "date" in historical_df.columns:
103
  historical_df = historical_df[historical_df["date"] >= cutoff_date]
104
  if "date" in forecast_df.columns:
105
  forecast_df = forecast_df[forecast_df["date"] >= cutoff_date]
106
 
107
- # Dictionary that maps metric names to the corresponding columns
108
  metrics = {
109
  "Revenue": {
110
  "historical": "revenue",
@@ -182,22 +188,19 @@ def main():
182
  name=f"Forecast {label}"
183
  ))
184
 
185
- # Analyst count differs if metric is EPS
186
  if metric_name == "EPS":
187
  analyst_field = "numberAnalystsEstimatedEps"
188
  else:
189
  analyst_field = "numberAnalystEstimatedRevenue"
190
 
191
- # Average number of analysts, if data present
192
  if analyst_field in forecast_df.columns and not forecast_df.empty:
193
  analysts_count = int(round(forecast_df[analyst_field].mean()))
194
  else:
195
  analysts_count = "N/A"
196
 
197
- # Title
198
  title_text = f"{ticker} - {metric_name} | Analysts: {analysts_count}"
199
 
200
- # Layout updates
201
  fig.update_layout(
202
  title=title_text,
203
  barmode="stack",
@@ -205,10 +208,10 @@ def main():
205
  paper_bgcolor="#0e1117",
206
  plot_bgcolor="#0e1117",
207
  xaxis=dict(
208
- title="Year",
209
  tickangle=45,
210
- tickformat="%Y",
211
- dtick="M12",
212
  showgrid=True,
213
  gridcolor="rgba(255, 255, 255, 0.1)"
214
  ),
@@ -227,37 +230,31 @@ def main():
227
  for metric, mapping in metrics.items():
228
  st.subheader(metric)
229
  st.write(
230
- f"This chart shows {metric} through the years. "
231
- f"Bars represent historical numbers. Lines represent various forecast ranges. "
232
- "Hover over the markers for details."
233
  )
234
-
235
  fig = create_plot(metric, mapping["historical"], mapping["forecast"])
236
  st.plotly_chart(fig, use_container_width=True)
237
 
238
- # Data expander at the end of each section
239
  with st.expander(f"View {metric} Data", expanded=False):
240
- # Show historical portion if available
241
  relevant_cols = []
242
  hc = mapping["historical"]
243
  if hc in historical_df.columns:
244
  relevant_cols.append(hc)
245
- # Include forecast columns if present
246
  for fc in mapping["forecast"].values():
247
  if fc in forecast_df.columns:
248
  relevant_cols.append(fc)
249
 
250
- # Merge data for display
251
- # We'll add a prefix to historical vs forecast columns to keep them separate
252
  hist_disp = historical_df[["date", hc]].copy() if hc in historical_df.columns else pd.DataFrame()
253
- hist_disp.rename(columns={hc: f"{metric}_Historical"}, inplace=True)
 
254
 
255
  forecast_disp = forecast_df[["date"] + list(mapping["forecast"].values())].copy() if not forecast_df.empty else pd.DataFrame()
256
  for fc_key, fc_val in mapping["forecast"].items():
257
  if fc_val in forecast_disp.columns:
258
  forecast_disp.rename(columns={fc_val: f"{metric}_Forecast_{fc_key}"}, inplace=True)
259
 
260
- # Merge on date if both are non-empty
261
  if not hist_disp.empty and not forecast_disp.empty:
262
  merged_df = pd.merge(hist_disp, forecast_disp, on="date", how="outer")
263
  merged_df.sort_values("date", inplace=True)
@@ -271,15 +268,12 @@ def main():
271
  if merged_df.empty:
272
  st.write("No data found for this metric.")
273
  else:
274
- # Show the data
275
  st.dataframe(merged_df.reset_index(drop=True))
276
 
277
  # ---- RUN ----
278
  if __name__ == "__main__":
279
  main()
280
 
281
-
282
- # Hide default Streamlit style
283
  st.markdown(
284
  """
285
  <style>
 
7
  import os
8
 
9
  # ---- PAGE CONFIG ----
 
10
  st.set_page_config(layout="wide")
11
 
12
  # ---- GLOBALS ----
 
16
  st.sidebar.title("User Inputs")
17
 
18
  with st.sidebar.expander("Configuration", expanded=True):
 
19
  ticker = st.text_input("Ticker:", "ASML", help="Insert the stock ticker.")
20
+
21
+ # Radio selection for Annual vs Quarterly data
22
+ data_period = st.radio("Select Data Period", ("Annual", "Quarterly"))
23
+
24
+ if data_period == "Annual":
25
+ period_api = "annual"
26
+ period_count = st.number_input(
27
+ "Years of historical data:",
28
+ min_value=1,
29
+ max_value=50,
30
+ value=15,
31
+ help="Choose how many years of historical data to retrieve."
32
+ )
33
+ cutoff_date = datetime.now() - dateutil.relativedelta.relativedelta(years=period_count)
34
+ xaxis_title = "Year"
35
+ tickformat = "%Y"
36
+ dtick = "M12"
37
+ else:
38
+ period_api = "quarter"
39
+ period_count = st.number_input(
40
+ "Quarters of historical data:",
41
+ min_value=1,
42
+ max_value=200,
43
+ value=20,
44
+ help="Choose how many quarters of historical data to retrieve."
45
+ )
46
+ cutoff_date = datetime.now() - dateutil.relativedelta.relativedelta(months=period_count * 3)
47
+ xaxis_title = "Quarter"
48
+ tickformat = "%Y-%m"
49
+ dtick = "M3"
50
 
 
51
  run_button = st.sidebar.button("Run Analysis")
52
 
 
53
  # ---- HELPER FUNCTION: VALUE FORMATTING ----
54
  def format_value(x):
 
55
  if abs(x) >= 1e9:
56
  return f"{x/1e9:.1f}B"
57
  elif abs(x) >= 1e6:
 
61
  else:
62
  return f"{x:.1f}"
63
 
 
64
  # ---- MAIN APP START ----
65
  def main():
66
  st.title("Analyst Forecasts & Estimates")
67
+ st.write("This tool fetches historical financial data and analyst forecasts. "
68
+ "It helps you see past trends and future estimates over your selected period.")
69
+
70
  if not run_button:
71
+ st.info("Set your inputs in the sidebar, then click **Run Analysis**.")
72
  return
73
 
 
74
  if not ticker.strip():
75
  st.error("Please enter a valid ticker.")
76
  return
77
 
78
  # ---- FETCH AND PREPARE DATA ----
 
79
  hist_url = (
80
  f"https://financialmodelingprep.com/api/v3/income-statement/{ticker}"
81
+ f"?period={period_api}&limit={period_count}&apikey={API_KEY}"
82
  )
83
  forecast_url = (
84
  f"https://financialmodelingprep.com/api/v3/analyst-estimates/{ticker}"
85
+ f"?period={period_api}&apikey={API_KEY}"
86
  )
 
87
  try:
 
88
  hist_data = requests.get(hist_url, timeout=10).json()
89
  forecast_data = requests.get(forecast_url, timeout=10).json()
90
  except Exception:
91
  st.error("Could not retrieve data at this time.")
92
  return
93
 
 
94
  historical_df = pd.DataFrame(hist_data)
95
  forecast_df = pd.DataFrame(forecast_data)
96
 
 
97
  if historical_df.empty and forecast_df.empty:
98
  st.warning("No data found for the specified ticker.")
99
  return
100
 
 
101
  if not historical_df.empty and "date" in historical_df.columns:
102
  historical_df["date"] = pd.to_datetime(historical_df["date"])
103
  historical_df.sort_values("date", inplace=True)
 
105
  forecast_df["date"] = pd.to_datetime(forecast_df["date"])
106
  forecast_df.sort_values("date", inplace=True)
107
 
108
+ # Filter data based on cutoff_date
 
 
 
109
  if "date" in historical_df.columns:
110
  historical_df = historical_df[historical_df["date"] >= cutoff_date]
111
  if "date" in forecast_df.columns:
112
  forecast_df = forecast_df[forecast_df["date"] >= cutoff_date]
113
 
 
114
  metrics = {
115
  "Revenue": {
116
  "historical": "revenue",
 
188
  name=f"Forecast {label}"
189
  ))
190
 
191
+ # Determine which analyst count field to display
192
  if metric_name == "EPS":
193
  analyst_field = "numberAnalystsEstimatedEps"
194
  else:
195
  analyst_field = "numberAnalystEstimatedRevenue"
196
 
 
197
  if analyst_field in forecast_df.columns and not forecast_df.empty:
198
  analysts_count = int(round(forecast_df[analyst_field].mean()))
199
  else:
200
  analysts_count = "N/A"
201
 
 
202
  title_text = f"{ticker} - {metric_name} | Analysts: {analysts_count}"
203
 
 
204
  fig.update_layout(
205
  title=title_text,
206
  barmode="stack",
 
208
  paper_bgcolor="#0e1117",
209
  plot_bgcolor="#0e1117",
210
  xaxis=dict(
211
+ title=xaxis_title,
212
  tickangle=45,
213
+ tickformat=tickformat,
214
+ dtick=dtick,
215
  showgrid=True,
216
  gridcolor="rgba(255, 255, 255, 0.1)"
217
  ),
 
230
  for metric, mapping in metrics.items():
231
  st.subheader(metric)
232
  st.write(
233
+ f"This chart shows {metric} over the selected time periods. "
234
+ f"Bars represent historical data and lines represent forecast ranges. "
235
+ "Hover over markers for details."
236
  )
 
237
  fig = create_plot(metric, mapping["historical"], mapping["forecast"])
238
  st.plotly_chart(fig, use_container_width=True)
239
 
 
240
  with st.expander(f"View {metric} Data", expanded=False):
 
241
  relevant_cols = []
242
  hc = mapping["historical"]
243
  if hc in historical_df.columns:
244
  relevant_cols.append(hc)
 
245
  for fc in mapping["forecast"].values():
246
  if fc in forecast_df.columns:
247
  relevant_cols.append(fc)
248
 
 
 
249
  hist_disp = historical_df[["date", hc]].copy() if hc in historical_df.columns else pd.DataFrame()
250
+ if not hist_disp.empty:
251
+ hist_disp.rename(columns={hc: f"{metric}_Historical"}, inplace=True)
252
 
253
  forecast_disp = forecast_df[["date"] + list(mapping["forecast"].values())].copy() if not forecast_df.empty else pd.DataFrame()
254
  for fc_key, fc_val in mapping["forecast"].items():
255
  if fc_val in forecast_disp.columns:
256
  forecast_disp.rename(columns={fc_val: f"{metric}_Forecast_{fc_key}"}, inplace=True)
257
 
 
258
  if not hist_disp.empty and not forecast_disp.empty:
259
  merged_df = pd.merge(hist_disp, forecast_disp, on="date", how="outer")
260
  merged_df.sort_values("date", inplace=True)
 
268
  if merged_df.empty:
269
  st.write("No data found for this metric.")
270
  else:
 
271
  st.dataframe(merged_df.reset_index(drop=True))
272
 
273
  # ---- RUN ----
274
  if __name__ == "__main__":
275
  main()
276
 
 
 
277
  st.markdown(
278
  """
279
  <style>