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:
parent
3a89c1e6d2
commit
8ba9d7ffdd
394
WATCHLIST_GUIDE.md
Normal file
394
WATCHLIST_GUIDE.md
Normal 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
179
scripts/fetch_congress_members.py
Executable 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")
|
||||
|
||||
306
scripts/generate_trading_report.py
Executable file
306
scripts/generate_trading_report.py
Executable 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()
|
||||
|
||||
85
scripts/pre_market_close_update.sh
Executable file
85
scripts/pre_market_close_update.sh
Executable 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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user