QuantumLearner commited on
Commit
b536f7f
·
verified ·
1 Parent(s): e4135eb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -62
app.py CHANGED
@@ -1,10 +1,11 @@
1
  import streamlit as st
2
- import requests
3
  import pandas as pd
4
  import altair as alt
5
  import datetime
6
  import re
7
  import os
 
 
8
 
9
  st.set_page_config(page_title="Congress Stock Trades", layout="wide")
10
 
@@ -13,15 +14,46 @@ API_KEY = os.getenv("FMP_API_KEY")
13
  SENATE_BASE_URL = "https://financialmodelingprep.com/api/v4/senate-trading-rss-feed"
14
  HOUSE_BASE_URL = "https://financialmodelingprep.com/api/v4/senate-disclosure-rss-feed"
15
 
16
- def fetch_data(base_url, pages=5):
17
- data = []
18
- for page in range(pages):
19
- url = f"{base_url}?page={page}&apikey={API_KEY}"
20
- r = requests.get(url)
21
- if r.status_code == 200:
22
- data.extend(r.json())
23
- return data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
 
 
 
25
  def parse_amount_range(amount_str):
26
  if not isinstance(amount_str, str):
27
  return None
@@ -35,21 +67,15 @@ def parse_amount_range(amount_str):
35
  match = re.match(r"\d+", clean_str)
36
  return float(match.group()) if match else None
37
 
38
- def load_data(base_url):
39
- raw_data = fetch_data(base_url, pages=5)
40
- if not raw_data:
41
- return pd.DataFrame()
42
- df = pd.DataFrame(raw_data)
43
- if "transactionDate" in df.columns:
44
- df["transactionDate"] = pd.to_datetime(df["transactionDate"], errors="coerce")
45
- df.sort_values(by="transactionDate", ascending=False, inplace=True)
46
- return df
47
-
48
  st.sidebar.title("Filters")
49
 
50
  with st.sidebar.expander("Parameters", expanded=True):
51
  start_date = st.date_input("Start transaction date", value=datetime.date(2025, 1, 1))
52
- top_n = st.slider("Top N stocks", min_value=1, max_value=20, value=10, help="Select the top N stock by trade amount and volume.")
 
53
 
54
  run_button = st.sidebar.button("Run Analysis")
55
 
@@ -57,32 +83,28 @@ st.title("Congress Trades Analysis")
57
  st.write("Analyze the latest trades reported by members of Congress. From the Senate and from the House.")
58
 
59
  if run_button:
60
- senate_data = load_data(SENATE_BASE_URL)
61
- house_data = load_data(HOUSE_BASE_URL)
 
62
 
 
63
  if not senate_data.empty:
64
- # Convert 'transactionDate' and 'dateRecieved' to datetime
65
  senate_data["transactionDate"] = pd.to_datetime(senate_data["transactionDate"], errors="coerce")
66
  senate_data["dateRecieved"] = pd.to_datetime(senate_data["dateRecieved"], errors="coerce")
67
-
68
- # Filter where either transactionDate or dateRecieved is on or after start_date
69
  senate_data = senate_data[
70
  (senate_data["transactionDate"] >= pd.to_datetime(start_date)) |
71
  (senate_data["dateRecieved"] >= pd.to_datetime(start_date))
72
  ]
 
73
  if not house_data.empty:
74
- # house_data = house_data[house_data["transactionDate"] >= pd.to_datetime(start_date)] gives issues
75
  house_data["transactionDate"] = pd.to_datetime(house_data["transactionDate"], errors="coerce")
76
  house_data["disclosureDate"] = pd.to_datetime(house_data["disclosureDate"], errors="coerce")
77
-
78
- # Filter where either transactionDate or disclosureDate is on or after start_date
79
  house_data = house_data[
80
  (house_data["transactionDate"] >= pd.to_datetime(start_date)) |
81
  (house_data["disclosureDate"] >= pd.to_datetime(start_date))
82
  ]
83
 
84
-
85
- # Prepare Senate
86
  senate_chart_data = pd.DataFrame()
87
  if not senate_data.empty:
88
  senate_chart_data = pd.DataFrame({
@@ -91,8 +113,7 @@ if run_button:
91
  "amount": senate_data["amount"].apply(parse_amount_range),
92
  "chamber": "Senate"
93
  })
94
-
95
- # Prepare House
96
  house_chart_data = pd.DataFrame()
97
  if not house_data.empty:
98
  house_chart_data = pd.DataFrame({
@@ -106,14 +127,12 @@ if run_button:
106
  combined_data.dropna(subset=["amount", "ticker"], inplace=True)
107
  combined_data = combined_data[combined_data["amount"] > 0]
108
 
109
- # Convert types to "purchase" or "sale"
110
  def standardize_trade_type(t):
111
  if "sale" in t or "sold" in t or "sell" in t:
112
  return "sale"
113
  return "purchase"
114
-
115
  combined_data["tradeType"] = combined_data["rawType"].apply(standardize_trade_type)
116
-
117
  combined_data["count"] = 1
118
 
119
  # Get top N by sum
@@ -135,7 +154,6 @@ if run_button:
135
  .groupby(["ticker", "chamber", "tradeType"], as_index=False)
136
  .agg({"amount": "sum", "count": "sum"})
137
  )
138
-
139
  base = alt.Chart(chart_data).encode(
140
  x=alt.X("ticker:N", axis=alt.Axis(labelAngle=-45)),
141
  xOffset="chamber:N",
@@ -145,44 +163,31 @@ if run_button:
145
  bars = base.mark_bar()
146
  text = base.mark_text(dy=-5, color="black").encode(text=alt.Text("count:Q"))
147
  chart = alt.layer(bars, text).properties(width=40 * len(top_tickers), height=400)
148
-
149
  st.altair_chart(chart, use_container_width=True)
150
 
151
- # Reorder columns for Senate
152
  if not senate_data.empty:
153
- # The order you specified:
154
- # 1) name
155
- # 2) disclosure/received date
156
- # 3) symbol
157
- # 4) purchase/sale
158
- # 5) amount
159
- # 6) assetDescription
160
- # We'll map "office" -> name, "dateRecieved" -> date, "symbol" -> symbol,
161
- # "type" -> purchase/sale, "amount" -> amount, "assetDescription" -> assetDescription
162
- # Then we append remaining columns
163
  desired_order_senate = [
164
  "office", # name
165
  "dateRecieved",
166
- "symbol", # symbol
167
- "type", # purchase/sale
168
- "amount", # amount
169
  "assetDescription"
170
  ]
171
- # Create an ordered list of columns that exist
172
  existing_senate_cols = [c for c in desired_order_senate if c in senate_data.columns]
173
- # Append the rest that we didn't list
174
  remaining_senate_cols = [c for c in senate_data.columns if c not in existing_senate_cols]
175
  reordered_senate_cols = existing_senate_cols + remaining_senate_cols
176
  senate_data = senate_data[reordered_senate_cols]
177
 
178
- # Reorder columns for House
179
  if not house_data.empty:
180
  desired_order_house = [
181
  "representative", # name
182
  "disclosureDate",
183
- "ticker", # symbol
184
- "type", # purchase/sale
185
- "amount", # amount
186
  "assetDescription"
187
  ]
188
  existing_house_cols = [c for c in desired_order_house if c in house_data.columns]
@@ -191,21 +196,19 @@ if run_button:
191
  house_data = house_data[reordered_house_cols]
192
 
193
  st.subheader("Senate Data")
194
- st.write("Latest Transaction in Senate. Please sort the table by **`disclosureDate`** and / or **`dateRecieved`** columns.")
195
  st.dataframe(senate_data, use_container_width=True)
196
 
197
  st.subheader("House Data")
198
- st.write("Latest Transaction in House. Please sort the table by the **`disclosureDate`** and / or **`transactionDate`** columns. ")
199
  st.dataframe(house_data, use_container_width=True)
200
-
201
  else:
202
  st.write("Set filters and press Run to load data.")
203
 
204
-
205
  hide_streamlit_style = """
206
  <style>
207
  #MainMenu {visibility: hidden;}
208
  footer {visibility: hidden;}
209
  </style>
210
  """
211
- st.markdown(hide_streamlit_style, unsafe_allow_html=True)
 
1
  import streamlit as st
 
2
  import pandas as pd
3
  import altair as alt
4
  import datetime
5
  import re
6
  import os
7
+ import asyncio
8
+ import aiohttp
9
 
10
  st.set_page_config(page_title="Congress Stock Trades", layout="wide")
11
 
 
14
  SENATE_BASE_URL = "https://financialmodelingprep.com/api/v4/senate-trading-rss-feed"
15
  HOUSE_BASE_URL = "https://financialmodelingprep.com/api/v4/senate-disclosure-rss-feed"
16
 
17
+ # ---------------------------
18
+ # ASYNC FUNCTIONS FOR FETCHING DATA
19
+ # ---------------------------
20
+ async def fetch_data_page(session, base_url, page):
21
+ url = f"{base_url}?page={page}&apikey={API_KEY}"
22
+ try:
23
+ async with session.get(url) as response:
24
+ if response.status == 200:
25
+ return await response.json()
26
+ else:
27
+ return [] # Fail gracefully
28
+ except Exception:
29
+ return []
30
+
31
+ async def fetch_all_data_async(base_url, pages=5):
32
+ async with aiohttp.ClientSession() as session:
33
+ tasks = [fetch_data_page(session, base_url, page) for page in range(pages)]
34
+ results = []
35
+ progress_bar = st.progress(0)
36
+ completed = 0
37
+ for coro in asyncio.as_completed(tasks):
38
+ data = await coro
39
+ results.extend(data)
40
+ completed += 1
41
+ progress_bar.progress(completed / pages)
42
+ return results
43
+
44
+ def load_data_async(base_url, pages=5):
45
+ raw_data = asyncio.run(fetch_all_data_async(base_url, pages))
46
+ if not raw_data:
47
+ return pd.DataFrame()
48
+ df = pd.DataFrame(raw_data)
49
+ if "transactionDate" in df.columns:
50
+ df["transactionDate"] = pd.to_datetime(df["transactionDate"], errors="coerce")
51
+ df.sort_values(by="transactionDate", ascending=False, inplace=True)
52
+ return df
53
 
54
+ # ---------------------------
55
+ # HELPER FUNCTION TO PARSE AMOUNT RANGE
56
+ # ---------------------------
57
  def parse_amount_range(amount_str):
58
  if not isinstance(amount_str, str):
59
  return None
 
67
  match = re.match(r"\d+", clean_str)
68
  return float(match.group()) if match else None
69
 
70
+ # ---------------------------
71
+ # MAIN APP CODE
72
+ # ---------------------------
 
 
 
 
 
 
 
73
  st.sidebar.title("Filters")
74
 
75
  with st.sidebar.expander("Parameters", expanded=True):
76
  start_date = st.date_input("Start transaction date", value=datetime.date(2025, 1, 1))
77
+ top_n = st.slider("Top N stocks", min_value=1, max_value=20, value=10,
78
+ help="Select the top N stock by trade amount and volume.")
79
 
80
  run_button = st.sidebar.button("Run Analysis")
81
 
 
83
  st.write("Analyze the latest trades reported by members of Congress. From the Senate and from the House.")
84
 
85
  if run_button:
86
+ # Use asynchronous fetching for both Senate and House
87
+ senate_data = load_data_async(SENATE_BASE_URL, pages=5)
88
+ house_data = load_data_async(HOUSE_BASE_URL, pages=5)
89
 
90
+ # Process Senate data
91
  if not senate_data.empty:
 
92
  senate_data["transactionDate"] = pd.to_datetime(senate_data["transactionDate"], errors="coerce")
93
  senate_data["dateRecieved"] = pd.to_datetime(senate_data["dateRecieved"], errors="coerce")
 
 
94
  senate_data = senate_data[
95
  (senate_data["transactionDate"] >= pd.to_datetime(start_date)) |
96
  (senate_data["dateRecieved"] >= pd.to_datetime(start_date))
97
  ]
98
+ # Process House data
99
  if not house_data.empty:
 
100
  house_data["transactionDate"] = pd.to_datetime(house_data["transactionDate"], errors="coerce")
101
  house_data["disclosureDate"] = pd.to_datetime(house_data["disclosureDate"], errors="coerce")
 
 
102
  house_data = house_data[
103
  (house_data["transactionDate"] >= pd.to_datetime(start_date)) |
104
  (house_data["disclosureDate"] >= pd.to_datetime(start_date))
105
  ]
106
 
107
+ # Prepare chart data for Senate
 
108
  senate_chart_data = pd.DataFrame()
109
  if not senate_data.empty:
110
  senate_chart_data = pd.DataFrame({
 
113
  "amount": senate_data["amount"].apply(parse_amount_range),
114
  "chamber": "Senate"
115
  })
116
+ # Prepare chart data for House
 
117
  house_chart_data = pd.DataFrame()
118
  if not house_data.empty:
119
  house_chart_data = pd.DataFrame({
 
127
  combined_data.dropna(subset=["amount", "ticker"], inplace=True)
128
  combined_data = combined_data[combined_data["amount"] > 0]
129
 
130
+ # Standardize trade type
131
  def standardize_trade_type(t):
132
  if "sale" in t or "sold" in t or "sell" in t:
133
  return "sale"
134
  return "purchase"
 
135
  combined_data["tradeType"] = combined_data["rawType"].apply(standardize_trade_type)
 
136
  combined_data["count"] = 1
137
 
138
  # Get top N by sum
 
154
  .groupby(["ticker", "chamber", "tradeType"], as_index=False)
155
  .agg({"amount": "sum", "count": "sum"})
156
  )
 
157
  base = alt.Chart(chart_data).encode(
158
  x=alt.X("ticker:N", axis=alt.Axis(labelAngle=-45)),
159
  xOffset="chamber:N",
 
163
  bars = base.mark_bar()
164
  text = base.mark_text(dy=-5, color="black").encode(text=alt.Text("count:Q"))
165
  chart = alt.layer(bars, text).properties(width=40 * len(top_tickers), height=400)
 
166
  st.altair_chart(chart, use_container_width=True)
167
 
168
+ # Reorder Senate columns
169
  if not senate_data.empty:
 
 
 
 
 
 
 
 
 
 
170
  desired_order_senate = [
171
  "office", # name
172
  "dateRecieved",
173
+ "symbol",
174
+ "type",
175
+ "amount",
176
  "assetDescription"
177
  ]
 
178
  existing_senate_cols = [c for c in desired_order_senate if c in senate_data.columns]
 
179
  remaining_senate_cols = [c for c in senate_data.columns if c not in existing_senate_cols]
180
  reordered_senate_cols = existing_senate_cols + remaining_senate_cols
181
  senate_data = senate_data[reordered_senate_cols]
182
 
183
+ # Reorder House columns
184
  if not house_data.empty:
185
  desired_order_house = [
186
  "representative", # name
187
  "disclosureDate",
188
+ "ticker",
189
+ "type",
190
+ "amount",
191
  "assetDescription"
192
  ]
193
  existing_house_cols = [c for c in desired_order_house if c in house_data.columns]
 
196
  house_data = house_data[reordered_house_cols]
197
 
198
  st.subheader("Senate Data")
199
+ st.write("Latest Transaction in Senate. Please sort the table by **`disclosureDate`** and/or **`dateRecieved`** columns.")
200
  st.dataframe(senate_data, use_container_width=True)
201
 
202
  st.subheader("House Data")
203
+ st.write("Latest Transaction in House. Please sort the table by **`disclosureDate`** and/or **`transactionDate`** columns.")
204
  st.dataframe(house_data, use_container_width=True)
 
205
  else:
206
  st.write("Set filters and press Run to load data.")
207
 
 
208
  hide_streamlit_style = """
209
  <style>
210
  #MainMenu {visibility: hidden;}
211
  footer {visibility: hidden;}
212
  </style>
213
  """
214
+ st.markdown(hide_streamlit_style, unsafe_allow_html=True)