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
117 lines
4.0 KiB
Python
Executable File
117 lines
4.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Calculate returns for all trades and display summary statistics.
|
|
"""
|
|
|
|
import argparse
|
|
import logging
|
|
|
|
from pote.analytics.metrics import PerformanceMetrics
|
|
from pote.db import get_session
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Calculate returns for all trades")
|
|
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)",
|
|
)
|
|
parser.add_argument(
|
|
"--top",
|
|
type=int,
|
|
default=10,
|
|
help="Number of top performers to show (default: 10)",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
with next(get_session()) as session:
|
|
metrics = PerformanceMetrics(session)
|
|
|
|
# Get system-wide statistics
|
|
logger.info("\n" + "=" * 70)
|
|
logger.info(" POTE System-Wide Performance Analysis")
|
|
logger.info("=" * 70)
|
|
|
|
summary = metrics.summary_statistics(
|
|
window_days=args.window,
|
|
benchmark=args.benchmark,
|
|
)
|
|
|
|
logger.info(f"\n📊 OVERALL STATISTICS")
|
|
logger.info("-" * 70)
|
|
logger.info(f"Total Officials: {summary['total_officials']}")
|
|
logger.info(f"Total Securities: {summary['total_securities']}")
|
|
logger.info(f"Total Trades: {summary['total_trades']}")
|
|
logger.info(f"Trades Analyzed: {summary.get('total_trades', 0)}")
|
|
logger.info(f"Window: {summary['window_days']} days")
|
|
logger.info(f"Benchmark: {summary['benchmark']}")
|
|
|
|
if summary.get('avg_alpha') is not None:
|
|
logger.info(f"\n🎯 AGGREGATE PERFORMANCE")
|
|
logger.info("-" * 70)
|
|
logger.info(f"Average Alpha: {float(summary['avg_alpha']):+.2f}%")
|
|
logger.info(f"Median Alpha: {float(summary['median_alpha']):+.2f}%")
|
|
logger.info(f"Max Alpha: {float(summary['max_alpha']):+.2f}%")
|
|
logger.info(f"Min Alpha: {float(summary['min_alpha']):+.2f}%")
|
|
logger.info(f"Beat Market Rate: {summary['beat_market_rate']:.1%}")
|
|
|
|
# Top performers
|
|
logger.info(f"\n🏆 TOP {args.top} PERFORMERS (by Alpha)")
|
|
logger.info("-" * 70)
|
|
|
|
top_performers = metrics.top_performers(
|
|
window_days=args.window,
|
|
benchmark=args.benchmark,
|
|
limit=args.top,
|
|
)
|
|
|
|
for i, perf in enumerate(top_performers, 1):
|
|
name = perf['name'][:25].ljust(25)
|
|
party = perf['party'][:3]
|
|
trades = perf['trades_analyzed']
|
|
alpha = float(perf['avg_alpha'])
|
|
logger.info(f"{i:2d}. {name} ({party}) | {trades:2d} trades | Alpha: {alpha:+6.2f}%")
|
|
|
|
# Sector analysis
|
|
logger.info(f"\n📊 PERFORMANCE BY SECTOR")
|
|
logger.info("-" * 70)
|
|
|
|
sectors = metrics.sector_analysis(
|
|
window_days=args.window,
|
|
benchmark=args.benchmark,
|
|
)
|
|
|
|
for sector_data in sectors:
|
|
sector = sector_data['sector'][:20].ljust(20)
|
|
count = sector_data['trade_count']
|
|
alpha = float(sector_data['avg_alpha'])
|
|
win_rate = sector_data['win_rate']
|
|
logger.info(f"{sector} | {count:3d} trades | Alpha: {alpha:+6.2f}% | Win: {win_rate:.1%}")
|
|
|
|
# Timing analysis
|
|
logger.info(f"\n⏱️ DISCLOSURE TIMING")
|
|
logger.info("-" * 70)
|
|
|
|
timing = metrics.timing_analysis()
|
|
if 'error' not in timing:
|
|
logger.info(f"Average Disclosure Lag: {timing['avg_disclosure_lag_days']:.1f} days")
|
|
logger.info(f"Median Disclosure Lag: {timing['median_disclosure_lag_days']} days")
|
|
logger.info(f"Max Disclosure Lag: {timing['max_disclosure_lag_days']} days")
|
|
|
|
logger.info("\n" + "=" * 70 + "\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|