Add watchlist system and pre-market trading reports

New Features:
- Watchlist system for tracking specific Congress members
- Trading report generation with multiple formats
- Pre-market-close automated updates (3 PM)

New Scripts:
- scripts/fetch_congress_members.py: Manage watchlist
  * 29 known active traders (curated list)
  * Optional ProPublica API integration (all 535 members)
  * Create/view/manage watchlist

- scripts/generate_trading_report.py: Generate trading reports
  * Filter by watchlist or show all
  * Multiple formats: text, HTML, JSON
  * Summary statistics (buys/sells, top tickers)
  * Color-coded output (🟢 BUY, 🔴 SELL)

- scripts/pre_market_close_update.sh: 3 PM automation
  * Quick fetch of latest trades
  * Enrichment of new securities
  * Generate and display report
  * Saves to reports/ directory

Documentation:
- WATCHLIST_GUIDE.md: Complete guide
  * List of 29 known active traders
  * How to create/customize watchlist
  * Schedule options (pre-market, post-market)
  * Email setup (optional)
  * FAQ and examples

Known Active Traders Include:
Senate: Tuberville, Rand Paul, Mark Warner, Rick Scott
House: Pelosi, Crenshaw, MTG, Gottheimer, Brian Higgins

Use Cases:
 Daily reports at 3 PM (1 hour before close)
 See what Congress bought/sold recently
 Track specific members you care about
 Export to HTML/JSON for further analysis
This commit is contained in:
ilia 2025-12-15 15:00:42 -05:00
parent 3a89c1e6d2
commit 8ba9d7ffdd
4 changed files with 964 additions and 0 deletions

394
WATCHLIST_GUIDE.md Normal file
View File

@ -0,0 +1,394 @@
# POTE Watchlist & Trading Reports
## 🎯 Get Trading Reports 1 Hour Before Market Close
### Quick Setup
```bash
# 1. Create watchlist of officials to monitor
python scripts/fetch_congress_members.py --create
# 2. Setup cron job for 3 PM ET (1 hour before close)
crontab -e
# Add this line:
0 15 * * 1-5 /home/poteapp/pote/scripts/pre_market_close_update.sh
# Save and exit
```
**What happens at 3 PM daily:**
1. Fetches latest trade disclosures
2. Enriches new securities
3. **Generates report** showing what was bought/sold
4. Saves to `reports/trading_report_YYYYMMDD.txt`
---
## 📋 Watchlist System
### Who's on the Default Watchlist?
**29 known active traders** based on 2023-2024 public reporting:
**Top Senate Traders:**
- Tommy Tuberville (R-AL) - Very active trader
- Rand Paul (R-KY) - Consistent activity
- Mark Warner (D-VA) - Tech sector focus
- Rick Scott (R-FL) - High volume
**Top House Traders:**
- Nancy Pelosi (D-CA) - Tech stocks, options
- Dan Crenshaw (R-TX) - Energy, defense
- Marjorie Taylor Greene (R-GA) - Various sectors
- Josh Gottheimer (D-NJ) - Financial services
- Brian Higgins (D-NY) - High frequency
[See full list: `python scripts/fetch_congress_members.py`]
---
## 🔧 Managing Your Watchlist
### View Current Watchlist
```bash
python scripts/fetch_congress_members.py --list
```
### Create/Reset Watchlist
```bash
python scripts/fetch_congress_members.py --create
```
Creates `config/watchlist.json` with default 29 active traders.
### Customize Watchlist
Edit `config/watchlist.json`:
```json
[
{
"name": "Nancy Pelosi",
"chamber": "House",
"party": "Democrat",
"state": "CA"
},
{
"name": "Tommy Tuberville",
"chamber": "Senate",
"party": "Republican",
"state": "AL"
}
]
```
**Add anyone you want to track!**
### Get ALL Members (Not Just Active Traders)
```bash
# 1. Get free API key from ProPublica:
# https://www.propublica.org/datastore/api/propublica-congress-api
# 2. Edit scripts/fetch_congress_members.py
# Add your API key
# 3. Run:
python scripts/fetch_congress_members.py --propublica
```
This fetches all 535 members of Congress (100 Senate + 435 House).
---
## 📊 Generating Reports
### Manual Report Generation
```bash
# Report from last 7 days (watchlist only)
python scripts/generate_trading_report.py --days 7 --watchlist-only
# Report from last 30 days (all officials)
python scripts/generate_trading_report.py --days 30
# Save to file
python scripts/generate_trading_report.py --output report.txt
# HTML format (for email)
python scripts/generate_trading_report.py --format html --output report.html
# JSON format (for programmatic use)
python scripts/generate_trading_report.py --format json --output report.json
```
### Example Report Output
```
================================================================================
CONGRESSIONAL TRADING REPORT
5 New Trades
Generated: 2024-12-15
================================================================================
────────────────────────────────────────────────────────────────────────────────
👤 Nancy Pelosi (D-CA, House)
────────────────────────────────────────────────────────────────────────────────
Side Ticker Company Sector Value Trade Date Filed
-------- ------ -------------------------- ---------- ------------------- ---------- ----------
🟢 BUY NVDA NVIDIA Corporation Technology $15,001 - $50,000 2024-11-15 2024-12-01
🔴 SELL MSFT Microsoft Corporation Technology $50,001 - $100,000 2024-11-20 2024-12-01
────────────────────────────────────────────────────────────────────────────────
👤 Tommy Tuberville (R-AL, Senate)
────────────────────────────────────────────────────────────────────────────────
Side Ticker Company Sector Value Trade Date Filed
-------- ------ -------------------------- ---------- ------------------- ---------- ----------
🟢 BUY SPY SPDR S&P 500 ETF Financial $100,001 - $250,000 2024-11-18 2024-12-02
🟢 BUY AAPL Apple Inc. Technology $50,001 - $100,000 2024-11-22 2024-12-02
🔴 SELL TSLA Tesla, Inc. Automotive $15,001 - $50,000 2024-11-25 2024-12-02
================================================================================
📊 SUMMARY
================================================================================
Total Trades: 5
Buys: 3
Sells: 2
Unique Officials: 2
Unique Tickers: 5
Top Tickers:
NVDA - 1 trades
MSFT - 1 trades
SPY - 1 trades
AAPL - 1 trades
TSLA - 1 trades
================================================================================
```
---
## ⏰ Automated Schedule Options
### Option 1: Pre-Market Close (3 PM ET) - Recommended
```bash
crontab -e
# Add: 0 15 * * 1-5 /home/poteapp/pote/scripts/pre_market_close_update.sh
```
**Why 3 PM?**
- 1 hour before market close (4 PM ET)
- Time to review report and make decisions
- Disclosures often appear during business hours
- Weekdays only (no weekends)
### Option 2: Pre-Market Open (8 AM ET)
```bash
crontab -e
# Add: 0 8 * * 1-5 /home/poteapp/pote/scripts/pre_market_close_update.sh
```
**Why 8 AM?**
- 30 minutes before market opens (9:30 AM ET)
- Catch overnight filings
- Review before trading day
### Option 3: After Market Close (5 PM ET)
```bash
crontab -e
# Add: 0 17 * * 1-5 /home/poteapp/pote/scripts/pre_market_close_update.sh
```
**Why 5 PM?**
- After market closes
- No trading pressure
- Full day of potential filings captured
### Option 4: Multiple Times Per Day
```bash
crontab -e
# Add: 0 8,15 * * 1-5 /home/poteapp/pote/scripts/pre_market_close_update.sh
```
Runs at 8 AM and 3 PM daily (weekdays).
---
## 📧 Email Reports (Optional)
### Setup Email Notifications
Edit `scripts/pre_market_close_update.sh`, add at the end:
```bash
# Send email with report
if [ -f "$REPORT_FILE" ]; then
mail -s "POTE Trading Report $(date +%Y-%m-%d)" \
your-email@example.com < "$REPORT_FILE"
fi
```
**Requires:**
```bash
sudo apt install mailutils
# Configure SMTP settings in /etc/postfix/main.cf
```
### HTML Email Reports
```bash
python scripts/generate_trading_report.py \
--format html \
--output /tmp/report.html
# Send HTML email
python scripts/send_email.py /tmp/report.html your-email@example.com
```
---
## 🎯 Typical Workflow
### Daily Routine (3 PM ET)
1. **Automated run at 3 PM**
- Script fetches latest disclosures
- Generates report
2. **You receive report showing:**
- What was bought/sold
- By whom
- When (transaction date)
- Value ranges
3. **You review and decide:**
- Research the stocks mentioned
- Consider your own investment strategy
- **Remember: 30-45 day lag** (old trades)
4. **Important:**
- This is for research/transparency
- Not investment advice
- Trades are 30-45 days old by law
---
## 🔍 Finding More Officials
### Public Resources
**High-Volume Traders:**
- https://housestockwatcher.com/
- https://senatestockwatcher.com/
- https://www.capitoltrades.com/
**Official Sources:**
- House Clerk: https://disclosures.house.gov/
- Senate: https://efdsearch.senate.gov/
**News Coverage:**
- "Unusual Whales" on Twitter/X
- Financial news sites
- ProPublica investigations
### Research Specific Committees
Members of certain committees tend to trade more:
**Senate:**
- Banking Committee (financial regulations)
- Armed Services (defense contracts)
- Energy Committee (energy stocks)
**House:**
- Financial Services
- Energy and Commerce
- Armed Services
Add committee members to your watchlist.
---
## 📈 Example Cron Setup
```bash
# Edit crontab
crontab -e
# Add these lines:
# Pre-market report (8 AM ET weekdays)
0 8 * * 1-5 /home/poteapp/pote/scripts/pre_market_close_update.sh
# Weekly full update (Sunday night)
0 0 * * 0 /home/poteapp/pote/scripts/daily_fetch.sh
# Save and exit
```
This gives you:
- Daily reports at 8 AM (weekdays)
- Weekly full system update (prices, analytics)
---
## 🚀 Quick Start Summary
```bash
# 1. Create watchlist
python scripts/fetch_congress_members.py --create
# 2. Test report generation
python scripts/generate_trading_report.py --days 7 --watchlist-only
# 3. Setup automation (3 PM daily)
crontab -e
# Add: 0 15 * * 1-5 /home/poteapp/pote/scripts/pre_market_close_update.sh
# 4. Check logs
tail -f logs/pre_market_*.log
# 5. View reports
cat reports/trading_report_$(date +%Y%m%d).txt
```
---
## ❓ FAQ
**Q: Why are all the trades old (30-45 days)?**
**A:** Federal law (STOCK Act) gives Congress 30-45 days to file. This is normal.
**Q: Can I track specific senators/representatives?**
**A:** Yes! Edit `config/watchlist.json` and add anyone.
**Q: Where's the full list of Congress members?**
**A:** Use `--propublica` option with a free API key to get all 535 members.
**Q: Can I get alerts for specific stocks?**
**A:** Yes! Modify `generate_trading_report.py` to filter by ticker.
**Q: What if House Stock Watcher is down?**
**A:** Reports will show existing data. Use CSV import for new data manually.
**Q: Can I track past trades?**
**A:** Yes! Adjust `--days` parameter: `--days 365` for full year.
---
**Ready to start tracking?**
```bash
python scripts/fetch_congress_members.py --create
python scripts/generate_trading_report.py --watchlist-only
```

179
scripts/fetch_congress_members.py Executable file
View File

@ -0,0 +1,179 @@
#!/usr/bin/env python
"""
Fetch current members of Congress.
Creates a watchlist of active members.
"""
import json
import requests
from pathlib import Path
# Congress.gov API (no key required for basic info)
# or ProPublica Congress API (requires free key)
def fetch_from_propublica():
"""
Fetch from ProPublica Congress API.
Free API key: https://www.propublica.org/datastore/api/propublica-congress-api
"""
API_KEY = "YOUR_API_KEY" # Get free key from ProPublica
headers = {"X-API-Key": API_KEY}
# Get current Congress (118th = 2023-2025, 119th = 2025-2027)
members = []
# Senate
senate_url = "https://api.propublica.org/congress/v1/118/senate/members.json"
response = requests.get(senate_url, headers=headers)
if response.ok:
data = response.json()
for member in data['results'][0]['members']:
members.append({
'name': f"{member['first_name']} {member['last_name']}",
'chamber': 'Senate',
'party': member['party'],
'state': member['state'],
})
# House
house_url = "https://api.propublica.org/congress/v1/118/house/members.json"
response = requests.get(house_url, headers=headers)
if response.ok:
data = response.json()
for member in data['results'][0]['members']:
members.append({
'name': f"{member['first_name']} {member['last_name']}",
'chamber': 'House',
'party': member['party'],
'state': member['state'],
})
return members
def get_known_active_traders():
"""
Manually curated list of Congress members known for active trading.
Based on public reporting and analysis.
"""
return [
# === SENATE ===
{"name": "Tommy Tuberville", "chamber": "Senate", "party": "Republican", "state": "AL"},
{"name": "Rand Paul", "chamber": "Senate", "party": "Republican", "state": "KY"},
{"name": "Sheldon Whitehouse", "chamber": "Senate", "party": "Democrat", "state": "RI"},
{"name": "John Hickenlooper", "chamber": "Senate", "party": "Democrat", "state": "CO"},
{"name": "Steve Daines", "chamber": "Senate", "party": "Republican", "state": "MT"},
{"name": "Gary Peters", "chamber": "Senate", "party": "Democrat", "state": "MI"},
{"name": "Rick Scott", "chamber": "Senate", "party": "Republican", "state": "FL"},
{"name": "Mark Warner", "chamber": "Senate", "party": "Democrat", "state": "VA"},
{"name": "Dianne Feinstein", "chamber": "Senate", "party": "Democrat", "state": "CA"}, # Note: deceased
# === HOUSE ===
{"name": "Nancy Pelosi", "chamber": "House", "party": "Democrat", "state": "CA"},
{"name": "Brian Higgins", "chamber": "House", "party": "Democrat", "state": "NY"},
{"name": "Michael McCaul", "chamber": "House", "party": "Republican", "state": "TX"},
{"name": "Dan Crenshaw", "chamber": "House", "party": "Republican", "state": "TX"},
{"name": "Marjorie Taylor Greene", "chamber": "House", "party": "Republican", "state": "GA"},
{"name": "Josh Gottheimer", "chamber": "House", "party": "Democrat", "state": "NJ"},
{"name": "Ro Khanna", "chamber": "House", "party": "Democrat", "state": "CA"},
{"name": "Dean Phillips", "chamber": "House", "party": "Democrat", "state": "MN"},
{"name": "Virginia Foxx", "chamber": "House", "party": "Republican", "state": "NC"},
{"name": "Debbie Wasserman Schultz", "chamber": "House", "party": "Democrat", "state": "FL"},
{"name": "Pat Fallon", "chamber": "House", "party": "Republican", "state": "TX"},
{"name": "Kevin Hern", "chamber": "House", "party": "Republican", "state": "OK"},
{"name": "Mark Green", "chamber": "House", "party": "Republican", "state": "TN"},
{"name": "French Hill", "chamber": "House", "party": "Republican", "state": "AR"},
{"name": "John Curtis", "chamber": "House", "party": "Republican", "state": "UT"},
# === HIGH VOLUME TRADERS (Based on 2023-2024 reporting) ===
{"name": "Austin Scott", "chamber": "House", "party": "Republican", "state": "GA"},
{"name": "Nicole Malliotakis", "chamber": "House", "party": "Republican", "state": "NY"},
{"name": "Lois Frankel", "chamber": "House", "party": "Democrat", "state": "FL"},
{"name": "Earl Blumenauer", "chamber": "House", "party": "Democrat", "state": "OR"},
{"name": "Pete Sessions", "chamber": "House", "party": "Republican", "state": "TX"},
]
def save_watchlist(members, filename="watchlist.json"):
"""Save watchlist to file."""
output_path = Path(__file__).parent.parent / "config" / filename
output_path.parent.mkdir(exist_ok=True)
with open(output_path, 'w') as f:
json.dump(members, f, indent=2)
print(f"✅ Saved {len(members)} members to {output_path}")
return output_path
def load_watchlist(filename="watchlist.json"):
"""Load watchlist from file."""
config_path = Path(__file__).parent.parent / "config" / filename
if not config_path.exists():
print(f"⚠️ Watchlist not found at {config_path}")
print(" Creating default watchlist...")
members = get_known_active_traders()
save_watchlist(members, filename)
return members
with open(config_path) as f:
return json.load(f)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Manage Congress member watchlist")
parser.add_argument("--create", action="store_true", help="Create default watchlist")
parser.add_argument("--list", action="store_true", help="List current watchlist")
parser.add_argument("--propublica", action="store_true", help="Fetch from ProPublica API")
args = parser.parse_args()
if args.propublica:
print("Fetching from ProPublica API...")
print("⚠️ You need to set API_KEY in the script first")
print(" Get free key: https://www.propublica.org/datastore/api/propublica-congress-api")
# members = fetch_from_propublica()
# save_watchlist(members)
elif args.create:
members = get_known_active_traders()
save_watchlist(members, "watchlist.json")
print(f"\n✅ Created watchlist with {len(members)} active traders")
elif args.list:
members = load_watchlist()
print(f"\n📋 Watchlist ({len(members)} members):\n")
# Group by chamber
senate = [m for m in members if m['chamber'] == 'Senate']
house = [m for m in members if m['chamber'] == 'House']
print("SENATE:")
for m in sorted(senate, key=lambda x: x['name']):
print(f"{m['name']:30s} ({m['party']:1s}-{m['state']})")
print("\nHOUSE:")
for m in sorted(house, key=lambda x: x['name']):
print(f"{m['name']:30s} ({m['party']:1s}-{m['state']})")
else:
# Default: show known active traders
members = get_known_active_traders()
print(f"\n📋 Known Active Traders ({len(members)} members):\n")
print("These are Congress members with historically high trading activity.\n")
senate = [m for m in members if m['chamber'] == 'Senate']
house = [m for m in members if m['chamber'] == 'House']
print("SENATE:")
for m in sorted(senate, key=lambda x: x['name']):
print(f"{m['name']:30s} ({m['party']:1s}-{m['state']})")
print("\nHOUSE:")
for m in sorted(house, key=lambda x: x['name']):
print(f"{m['name']:30s} ({m['party']:1s}-{m['state']})")
print("\n💡 To create watchlist file: python scripts/fetch_congress_members.py --create")
print("💡 To view saved watchlist: python scripts/fetch_congress_members.py --list")

View File

@ -0,0 +1,306 @@
#!/usr/bin/env python
"""
Generate trading report for watched Congress members.
Shows NEW trades filed recently.
"""
import json
from datetime import date, timedelta
from pathlib import Path
from decimal import Decimal
import click
from sqlalchemy import text
from tabulate import tabulate
from pote.db import get_session
from pote.db.models import Official, Security, Trade
def load_watchlist():
"""Load watchlist of officials to monitor."""
config_path = Path(__file__).parent.parent / "config" / "watchlist.json"
if not config_path.exists():
print("⚠️ No watchlist found. Creating default...")
import subprocess
subprocess.run([
"python",
str(Path(__file__).parent / "fetch_congress_members.py"),
"--create"
])
with open(config_path) as f:
return json.load(f)
def get_new_trades(session, days=7, watchlist=None):
"""
Get trades filed in the last N days.
Args:
session: Database session
days: Look back this many days
watchlist: List of official names to filter (None = all)
"""
since_date = date.today() - timedelta(days=days)
query = text("""
SELECT
o.name,
o.chamber,
o.party,
o.state,
s.ticker,
s.name as company,
s.sector,
t.side,
t.transaction_date,
t.filing_date,
t.value_min,
t.value_max,
t.created_at
FROM trades t
JOIN officials o ON t.official_id = o.id
JOIN securities s ON t.security_id = s.id
WHERE t.created_at >= :since_date
ORDER BY t.created_at DESC, t.transaction_date DESC
""")
result = session.execute(query, {"since_date": since_date})
trades = result.fetchall()
# Filter by watchlist if provided
if watchlist:
watchlist_names = {m['name'].lower() for m in watchlist}
trades = [t for t in trades if t[0].lower() in watchlist_names]
return trades
def format_value(vmin, vmax):
"""Format trade value range."""
if vmax and vmax > vmin:
return f"${float(vmin):,.0f} - ${float(vmax):,.0f}"
else:
return f"${float(vmin):,.0f}+"
def generate_report(trades, format="text"):
"""Generate formatted report."""
if not trades:
return "📭 No new trades found."
if format == "text":
return generate_text_report(trades)
elif format == "html":
return generate_html_report(trades)
elif format == "json":
return generate_json_report(trades)
else:
return generate_text_report(trades)
def generate_text_report(trades):
"""Generate text report."""
report = []
report.append(f"\n{'='*80}")
report.append(f" CONGRESSIONAL TRADING REPORT")
report.append(f" {len(trades)} New Trades")
report.append(f" Generated: {date.today()}")
report.append(f"{'='*80}\n")
# Group by official
by_official = {}
for trade in trades:
name = trade[0]
if name not in by_official:
by_official[name] = []
by_official[name].append(trade)
# Generate section for each official
for official_name, official_trades in by_official.items():
# Header
first_trade = official_trades[0]
chamber, party, state = first_trade[1], first_trade[2], first_trade[3]
report.append(f"\n{''*80}")
report.append(f"👤 {official_name} ({party[0]}-{state}, {chamber})")
report.append(f"{''*80}")
# Trades table
table_data = []
for t in official_trades:
ticker, company, sector = t[4], t[5], t[6]
side, txn_date, filing_date = t[7], t[8], t[9]
vmin, vmax = t[10], t[11]
# Color code side
side_emoji = "🟢 BUY" if side.lower() == "buy" else "🔴 SELL"
table_data.append([
side_emoji,
ticker,
f"{company[:30]}..." if company and len(company) > 30 else (company or ""),
sector or "",
format_value(vmin, vmax),
str(txn_date),
str(filing_date),
])
table = tabulate(
table_data,
headers=["Side", "Ticker", "Company", "Sector", "Value", "Trade Date", "Filed"],
tablefmt="simple"
)
report.append(table)
report.append("")
# Summary statistics
report.append(f"\n{'='*80}")
report.append("📊 SUMMARY")
report.append(f"{'='*80}")
total_buys = sum(1 for t in trades if t[7].lower() == "buy")
total_sells = sum(1 for t in trades if t[7].lower() == "sell")
unique_tickers = len(set(t[4] for t in trades))
unique_officials = len(by_official)
# Top tickers
ticker_counts = {}
for t in trades:
ticker = t[4]
ticker_counts[ticker] = ticker_counts.get(ticker, 0) + 1
top_tickers = sorted(ticker_counts.items(), key=lambda x: x[1], reverse=True)[:5]
report.append(f"\nTotal Trades: {len(trades)}")
report.append(f" Buys: {total_buys}")
report.append(f" Sells: {total_sells}")
report.append(f"Unique Officials: {unique_officials}")
report.append(f"Unique Tickers: {unique_tickers}")
report.append(f"\nTop Tickers:")
for ticker, count in top_tickers:
report.append(f" {ticker:6s} - {count} trades")
report.append(f"\n{'='*80}\n")
return "\n".join(report)
def generate_html_report(trades):
"""Generate HTML email report."""
html = [
"<html><head><style>",
"body { font-family: Arial, sans-serif; }",
"table { border-collapse: collapse; width: 100%; margin: 20px 0; }",
"th { background: #333; color: white; padding: 10px; text-align: left; }",
"td { border: 1px solid #ddd; padding: 8px; }",
".buy { color: green; font-weight: bold; }",
".sell { color: red; font-weight: bold; }",
"</style></head><body>",
f"<h1>Congressional Trading Report</h1>",
f"<p><strong>{len(trades)} New Trades</strong> | Generated: {date.today()}</p>",
]
# Group by official
by_official = {}
for trade in trades:
name = trade[0]
if name not in by_official:
by_official[name] = []
by_official[name].append(trade)
for official_name, official_trades in by_official.items():
first_trade = official_trades[0]
chamber, party, state = first_trade[1], first_trade[2], first_trade[3]
html.append(f"<h2>{official_name} ({party[0]}-{state}, {chamber})</h2>")
html.append("<table>")
html.append("<tr><th>Side</th><th>Ticker</th><th>Company</th><th>Value</th><th>Trade Date</th><th>Filed</th></tr>")
for t in official_trades:
ticker, company, sector = t[4], t[5], t[6]
side, txn_date, filing_date = t[7], t[8], t[9]
vmin, vmax = t[10], t[11]
side_class = "buy" if side.lower() == "buy" else "sell"
side_text = "BUY" if side.lower() == "buy" else "SELL"
html.append(f"<tr>")
html.append(f"<td class='{side_class}'>{side_text}</td>")
html.append(f"<td><strong>{ticker}</strong></td>")
html.append(f"<td>{company or ''}</td>")
html.append(f"<td>{format_value(vmin, vmax)}</td>")
html.append(f"<td>{txn_date}</td>")
html.append(f"<td>{filing_date}</td>")
html.append(f"</tr>")
html.append("</table>")
html.append("</body></html>")
return "\n".join(html)
def generate_json_report(trades):
"""Generate JSON report for programmatic use."""
import json
trades_list = []
for t in trades:
trades_list.append({
"official": t[0],
"chamber": t[1],
"party": t[2],
"state": t[3],
"ticker": t[4],
"company": t[5],
"sector": t[6],
"side": t[7],
"transaction_date": str(t[8]),
"filing_date": str(t[9]),
"value_min": float(t[10]),
"value_max": float(t[11]) if t[11] else None,
})
return json.dumps({
"generated": str(date.today()),
"trade_count": len(trades),
"trades": trades_list
}, indent=2)
@click.command()
@click.option("--days", default=7, help="Look back this many days")
@click.option("--watchlist-only", is_flag=True, help="Only show trades from watchlist")
@click.option("--format", type=click.Choice(["text", "html", "json"]), default="text")
@click.option("--output", help="Output file (default: stdout)")
def main(days, watchlist_only, format, output):
"""Generate trading report for Congress members."""
session = next(get_session())
# Load watchlist if requested
watchlist = None
if watchlist_only:
watchlist = load_watchlist()
print(f"📋 Filtering for {len(watchlist)} officials on watchlist\n")
# Get trades
print(f"🔍 Fetching trades from last {days} days...")
trades = get_new_trades(session, days=days, watchlist=watchlist)
# Generate report
report = generate_report(trades, format=format)
# Output
if output:
Path(output).write_text(report)
print(f"✅ Report saved to {output}")
else:
print(report)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,85 @@
#!/bin/bash
# Pre-Market-Close POTE Update
# Run at 3 PM ET (1 hour before market close)
# Fetches latest disclosures and generates actionable report
set -e
# --- Configuration ---
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
LOG_DIR="${PROJECT_DIR}/logs"
REPORT_DIR="${PROJECT_DIR}/reports"
LOG_FILE="${LOG_DIR}/pre_market_$(date +%Y%m%d).log"
REPORT_FILE="${REPORT_DIR}/trading_report_$(date +%Y%m%d).txt"
# Ensure directories exist
mkdir -p "$LOG_DIR" "$REPORT_DIR"
# Redirect output to log
exec > >(tee -a "$LOG_FILE") 2>&1
echo "=========================================="
echo " POTE Pre-Market-Close Update"
echo " $(date)"
echo " Running 1 hour before market close"
echo "=========================================="
# Activate virtual environment
cd "$PROJECT_DIR"
source venv/bin/activate
# --- Step 1: Quick Fetch of New Trades ---
echo ""
echo "--- Fetching Latest Congressional Trades ---"
python scripts/fetch_congressional_trades.py --days 3
FETCH_EXIT=$?
if [ $FETCH_EXIT -ne 0 ]; then
echo "⚠️ WARNING: Failed to fetch trades (API may be down)"
echo " Generating report from existing data..."
fi
# --- Step 2: Quick Security Enrichment ---
echo ""
echo "--- Enriching New Securities ---"
python scripts/enrich_securities.py --limit 10
ENRICH_EXIT=$?
# --- Step 3: Generate Trading Report ---
echo ""
echo "--- Generating Trading Report ---"
python scripts/generate_trading_report.py \
--days 7 \
--watchlist-only \
--format text \
--output "$REPORT_FILE"
REPORT_EXIT=$?
if [ $REPORT_EXIT -eq 0 ]; then
echo "✅ Report saved to: $REPORT_FILE"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📊 REPORT PREVIEW"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
cat "$REPORT_FILE"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
else
echo "❌ Failed to generate report"
fi
# --- Step 4: Optional Price Update (Quick) ---
# Uncomment if you want prices updated before market close
# echo ""
# echo "--- Quick Price Update ---"
# python scripts/fetch_sample_prices.py --days 5
echo ""
echo "=========================================="
echo " Update Complete - $(date)"
echo "=========================================="
# Exit successfully even if some steps warned
exit 0