diff --git a/WATCHLIST_GUIDE.md b/WATCHLIST_GUIDE.md new file mode 100644 index 0000000..8fdd88e --- /dev/null +++ b/WATCHLIST_GUIDE.md @@ -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 +``` + diff --git a/scripts/fetch_congress_members.py b/scripts/fetch_congress_members.py new file mode 100755 index 0000000..7e0deaa --- /dev/null +++ b/scripts/fetch_congress_members.py @@ -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") + diff --git a/scripts/generate_trading_report.py b/scripts/generate_trading_report.py new file mode 100755 index 0000000..182c883 --- /dev/null +++ b/scripts/generate_trading_report.py @@ -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 = [ + "", + f"

Congressional Trading Report

", + f"

{len(trades)} New Trades | Generated: {date.today()}

", + ] + + # 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"

{official_name} ({party[0]}-{state}, {chamber})

") + html.append("") + html.append("") + + 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"") + html.append(f"") + html.append(f"") + html.append(f"") + html.append(f"") + html.append(f"") + html.append(f"") + html.append(f"") + + html.append("
SideTickerCompanyValueTrade DateFiled
{side_text}{ticker}{company or ''}{format_value(vmin, vmax)}{txn_date}{filing_date}
") + + html.append("") + 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() + diff --git a/scripts/pre_market_close_update.sh b/scripts/pre_market_close_update.sh new file mode 100755 index 0000000..91a4846 --- /dev/null +++ b/scripts/pre_market_close_update.sh @@ -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 +