Complete analytics module with returns, benchmarks, and performance metrics. New Modules: - src/pote/analytics/returns.py: Return calculator for trades - src/pote/analytics/benchmarks.py: Benchmark comparison & alpha - src/pote/analytics/metrics.py: Performance aggregations Scripts: - scripts/analyze_official.py: Analyze specific official - scripts/calculate_all_returns.py: System-wide analysis Tests: - tests/test_analytics.py: Full coverage of analytics Features: ✅ Calculate returns over 30/60/90/180 day windows ✅ Compare to market benchmarks (SPY, QQQ, etc.) ✅ Calculate abnormal returns (alpha) ✅ Aggregate stats by official, sector ✅ Top performer rankings ✅ Disclosure timing analysis ✅ Command-line analysis tools ~1,210 lines of new code, all tested
141 lines
4.4 KiB
Python
Executable File
141 lines
4.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Analyze performance of a specific official.
|
|
"""
|
|
|
|
import argparse
|
|
import logging
|
|
import sys
|
|
|
|
from pote.analytics.metrics import PerformanceMetrics
|
|
from pote.db import get_session
|
|
from pote.db.models import Official
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def format_pct(value):
|
|
"""Format percentage."""
|
|
return f"{float(value):+.2f}%"
|
|
|
|
|
|
def format_money(value):
|
|
"""Format money."""
|
|
return f"${float(value):,.0f}"
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Analyze official's trading performance")
|
|
parser.add_argument("name", help="Official's name (e.g., 'Nancy Pelosi')")
|
|
parser.add_argument(
|
|
"--window",
|
|
type=int,
|
|
default=90,
|
|
help="Return window in days (default: 90)",
|
|
)
|
|
parser.add_argument(
|
|
"--benchmark",
|
|
default="SPY",
|
|
help="Benchmark ticker (default: SPY)",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
with next(get_session()) as session:
|
|
# Find official
|
|
official = (
|
|
session.query(Official)
|
|
.filter(Official.name.ilike(f"%{args.name}%"))
|
|
.first()
|
|
)
|
|
|
|
if not official:
|
|
logger.error(f"Official not found: {args.name}")
|
|
logger.info("Available officials:")
|
|
for o in session.query(Official).all():
|
|
logger.info(f" - {o.name}")
|
|
sys.exit(1)
|
|
|
|
# Get performance metrics
|
|
metrics = PerformanceMetrics(session)
|
|
perf = metrics.official_performance(
|
|
official.id,
|
|
window_days=args.window,
|
|
benchmark=args.benchmark,
|
|
)
|
|
|
|
# Display results
|
|
print()
|
|
print("=" * 70)
|
|
print(f" {perf['name']} Performance Analysis")
|
|
print("=" * 70)
|
|
print()
|
|
print(f"Party: {perf['party']}")
|
|
print(f"Chamber: {perf['chamber']}")
|
|
print(f"State: {perf['state']}")
|
|
print(f"Window: {perf['window_days']} days")
|
|
print(f"Benchmark: {perf['benchmark']}")
|
|
print()
|
|
|
|
if perf.get("trades_analyzed", 0) == 0:
|
|
print("⚠️ No trades with sufficient price data to analyze")
|
|
sys.exit(0)
|
|
|
|
print("📊 TRADING ACTIVITY")
|
|
print("-" * 70)
|
|
print(f"Total Trades: {perf['total_trades']}")
|
|
print(f"Analyzed: {perf['trades_analyzed']}")
|
|
print(f"Buy Trades: {perf['buy_trades']}")
|
|
print(f"Sell Trades: {perf['sell_trades']}")
|
|
print(f"Total Value: {format_money(perf['total_value_traded'])}")
|
|
print()
|
|
|
|
print("📈 PERFORMANCE METRICS")
|
|
print("-" * 70)
|
|
print(f"Average Return: {format_pct(perf['avg_return'])}")
|
|
print(f"Median Return: {format_pct(perf['median_return'])}")
|
|
print(f"Max Return: {format_pct(perf['max_return'])}")
|
|
print(f"Min Return: {format_pct(perf['min_return'])}")
|
|
print()
|
|
|
|
print("🎯 VS MARKET ({})".format(perf['benchmark']))
|
|
print("-" * 70)
|
|
print(f"Average Alpha: {format_pct(perf['avg_alpha'])}")
|
|
print(f"Median Alpha: {format_pct(perf['median_alpha'])}")
|
|
print(f"Win Rate: {perf['win_rate']:.1%}")
|
|
print(f"Beat Market Rate: {perf['beat_market_rate']:.1%}")
|
|
print()
|
|
|
|
print("🏆 BEST/WORST TRADES")
|
|
print("-" * 70)
|
|
best = perf['best_trade']
|
|
worst = perf['worst_trade']
|
|
print(f"Best: {best['ticker']:6s} {format_pct(best['return']):>10s} ({best['date']})")
|
|
print(f"Worst: {worst['ticker']:6s} {format_pct(worst['return']):>10s} ({worst['date']})")
|
|
print()
|
|
|
|
# Signal
|
|
alpha = float(perf['avg_alpha'])
|
|
beat_rate = perf['beat_market_rate']
|
|
|
|
print("🔔 RESEARCH SIGNAL")
|
|
print("-" * 70)
|
|
if alpha > 5 and beat_rate > 0.65:
|
|
print("✅ FOLLOW_RESEARCH: Strong positive alpha with high win rate")
|
|
elif alpha > 2 and beat_rate > 0.55:
|
|
print("⭐ FOLLOW_RESEARCH: Moderate positive alpha")
|
|
elif alpha < -5 or beat_rate < 0.35:
|
|
print("🚨 AVOID_RISK: Negative alpha or poor performance")
|
|
elif perf['total_trades'] < 5:
|
|
print("👀 WATCH: Limited data, need more trades for confidence")
|
|
else:
|
|
print("📊 NEUTRAL: Performance close to market")
|
|
print()
|
|
|
|
print("=" * 70)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|