- scripts/add_custom_trades.py: Manual trade entry - scripts/scrape_alternative_sources.py: CSV import - scripts/daily_update.sh: Automated daily updates - docs/09_data_updates.md: Complete update guide - docs/PR4_PLAN.md: Phase 2 analytics plan Enables users to add representatives and set up auto-updates
6.4 KiB
6.4 KiB
PR4: Phase 2 - Analytics Foundation
Goal
Calculate abnormal returns and performance metrics for congressional trades.
What We'll Build
1. Return Calculator (src/pote/analytics/returns.py)
class ReturnCalculator:
"""Calculate returns for trades over various windows."""
def calculate_trade_return(
self,
trade: Trade,
window_days: int = 90
) -> dict:
"""
Calculate return for a single trade.
Returns:
{
'ticker': 'NVDA',
'transaction_date': '2024-01-15',
'window_days': 90,
'entry_price': 495.00,
'exit_price': 650.00,
'return_pct': 31.3,
'return_abs': 155.00
}
"""
pass
def calculate_benchmark_return(
self,
start_date: date,
end_date: date,
benchmark: str = "SPY" # S&P 500
) -> float:
"""Calculate benchmark return over period."""
pass
def calculate_abnormal_return(
self,
trade_return: float,
benchmark_return: float
) -> float:
"""Return - Benchmark = Abnormal Return (alpha)."""
return trade_return - benchmark_return
2. Performance Metrics (src/pote/analytics/metrics.py)
class PerformanceMetrics:
"""Aggregate performance metrics by official, sector, etc."""
def official_performance(
self,
official_id: int,
window_days: int = 90
) -> dict:
"""
Aggregate stats for an official.
Returns:
{
'name': 'Nancy Pelosi',
'total_trades': 50,
'buy_trades': 35,
'sell_trades': 15,
'avg_return': 12.5,
'avg_abnormal_return': 5.2,
'win_rate': 0.68,
'total_value': 2500000,
'best_trade': {'ticker': 'NVDA', 'return': 85.3},
'worst_trade': {'ticker': 'META', 'return': -15.2}
}
"""
pass
def sector_analysis(self, window_days: int = 90) -> list:
"""Performance by sector (Tech, Healthcare, etc.)."""
pass
def timing_analysis(self) -> dict:
"""Analyze disclosure lag vs performance."""
pass
3. Benchmark Comparisons (src/pote/analytics/benchmarks.py)
class BenchmarkComparison:
"""Compare official performance vs market indices."""
BENCHMARKS = {
'SPY': 'S&P 500',
'QQQ': 'NASDAQ-100',
'DIA': 'Dow Jones',
'IWM': 'Russell 2000'
}
def compare_to_market(
self,
official_id: int,
benchmark: str = 'SPY',
period_start: date = None
) -> dict:
"""
Compare official's returns to market.
Returns:
{
'official_return': 15.2,
'benchmark_return': 8.5,
'alpha': 6.7,
'sharpe_ratio': 1.35,
'win_rate_vs_market': 0.72
}
"""
pass
4. Database Schema Updates
Add metrics_performance table:
CREATE TABLE metrics_performance (
id SERIAL PRIMARY KEY,
official_id INTEGER REFERENCES officials(id),
security_id INTEGER REFERENCES securities(id),
trade_id INTEGER REFERENCES trades(id),
-- Return metrics
window_days INTEGER NOT NULL,
entry_price DECIMAL(15, 2),
exit_price DECIMAL(15, 2),
return_pct DECIMAL(10, 4),
return_abs DECIMAL(15, 2),
-- Benchmark comparison
benchmark_ticker VARCHAR(10),
benchmark_return_pct DECIMAL(10, 4),
abnormal_return_pct DECIMAL(10, 4), -- alpha
-- Calculated at
calculated_at TIMESTAMP,
INDEX(official_id, window_days),
INDEX(security_id, window_days),
INDEX(trade_id)
);
Implementation Steps
-
Create analytics module structure
src/pote/analytics/ ├── __init__.py ├── returns.py # Return calculations ├── metrics.py # Aggregate metrics ├── benchmarks.py # Benchmark comparisons └── utils.py # Helper functions -
Add database migration
alembic revision -m "add_performance_metrics_table" -
Implement return calculator
- Fetch prices from database
- Calculate returns for various windows (30, 60, 90, 180 days)
- Handle edge cases (IPOs, delisting, missing data)
-
Implement benchmark comparisons
- Fetch benchmark data (SPY, QQQ, etc.)
- Calculate abnormal returns
- Statistical significance tests
-
Create calculation scripts
scripts/calculate_returns.py # Calculate all returns scripts/update_metrics.py # Update performance table scripts/analyze_official.py # Analyze specific official -
Add tests
- Unit tests for calculators
- Integration tests with sample data
- Edge case handling
Example Usage
# Calculate returns for all trades
from pote.analytics.returns import ReturnCalculator
from pote.db import get_session
calculator = ReturnCalculator()
with next(get_session()) as session:
trades = session.query(Trade).all()
for trade in trades:
for window in [30, 60, 90]:
result = calculator.calculate_trade_return(trade, window)
print(f"{trade.official.name} {trade.security.ticker}: "
f"{result['return_pct']:.1f}% ({window}d)")
# Get official performance summary
from pote.analytics.metrics import PerformanceMetrics
metrics = PerformanceMetrics()
pelosi_stats = metrics.official_performance(official_id=1, window_days=90)
print(f"Average Return: {pelosi_stats['avg_return']:.1f}%")
print(f"Alpha: {pelosi_stats['avg_abnormal_return']:.1f}%")
print(f"Win Rate: {pelosi_stats['win_rate']:.1%}")
Success Criteria
- ✅ Can calculate returns for any trade + window
- ✅ Can compare to S&P 500 benchmark
- ✅ Can generate official performance summaries
- ✅ All calculations tested and accurate
- ✅ Performance data stored efficiently
- ✅ Documentation complete
Timeline
- Implementation: 2-3 hours
- Testing: 1 hour
- Documentation: 30 minutes
- Total: ~4 hours
Next Steps After PR4
PR5: Clustering & Behavioral Analysis PR6: Research Signals (follow_research, avoid_risk, watch) PR7: API & Dashboard