#!/usr/bin/env python3 """ Command Line Interface for yfinance-enhanced Provides cache management and utility functions """ import argparse import sys import json import pickle from pathlib import Path from datetime import datetime try: from yfinance_plus import get_cache_info, clear_cache, HistoricalDataCache, RequestConfig except ImportError: print("Error: yfinance_plus module not found. Please install it first.") sys.exit(1) def format_size(size_bytes): """Format size in bytes to human readable format""" if size_bytes == 0: return "0 B" for unit in ['B', 'KB', 'MB', 'GB']: if size_bytes < 1024.0: return f"{size_bytes:.1f} {unit}" size_bytes /= 1024.0 return f"{size_bytes:.1f} TB" def get_cache_file_details(cache_dir, filename): """Load and analyze cache file to get details about stored data""" try: cache_file = Path(cache_dir) / filename if not cache_file.exists(): return None with open(cache_file, 'rb') as f: cache_data = pickle.load(f) # Extract details from cache data details = { 'symbol': cache_data.get('symbol', 'Unknown'), 'period': cache_data.get('period', 'Unknown'), 'interval': cache_data.get('interval', '1d'), 'rows': len(cache_data.get('data', [])), 'date_range': None } # Try to get date range from the data data = cache_data.get('data') if data is not None and hasattr(data, 'index') and len(data) > 0: try: start_date = data.index[0].strftime('%Y-%m-%d') end_date = data.index[-1].strftime('%Y-%m-%d') details['date_range'] = f"{start_date} to {end_date}" except: pass return details except Exception as e: # If we can't read the cache file, return minimal info return { 'symbol': 'Error', 'period': f'({str(e)[:20]}...)', 'interval': '?', 'rows': 0, 'date_range': None } def cmd_cache_info(args): """Show cache information""" try: info = get_cache_info() print("šŸ—‚ļø YFinance Plus Cache Information") print("=" * 50) print(f"Cache Directory: {info['cache_dir']}") print(f"Number of Files: {info['file_count']}") print(f"Total Size: {format_size(info['total_size_mb'] * 1024 * 1024)}") if info['file_count'] > 0: print(f"\nšŸ“ Cache Files:") print("-" * 100) sorted_files = sorted(info['files'], key=lambda x: x['modified'], reverse=True) for file_info in sorted_files[:args.limit if args.limit else len(sorted_files)]: size_str = format_size(file_info['size_kb'] * 1024) modified_str = file_info['modified'].strftime('%Y-%m-%d %H:%M:%S') # Try to load and analyze cache file content cache_details = get_cache_file_details(info['cache_dir'], file_info['name']) print(f" šŸ“„ {file_info['name'][:32]:<32} {size_str:>8} {modified_str}") if cache_details: print(f" Symbol: {cache_details['symbol']:<8} Period: {cache_details['period']:<6} " f"Interval: {cache_details['interval']:<4} Rows: {cache_details['rows']}") if cache_details['date_range']: print(f" Date Range: {cache_details['date_range']}") else: print(f" āš ļø Could not read cache file details") print() if args.limit and len(sorted_files) > args.limit: print(f" ... and {len(sorted_files) - args.limit} more files") else: print("\nšŸ’” No cache files found") except Exception as e: print(f"āŒ Error getting cache info: {e}") sys.exit(1) def cmd_cache_clear(args): """Clear cache""" if not args.force: try: info = get_cache_info() if info['file_count'] > 0: response = input(f"āš ļø This will delete {info['file_count']} cache files " f"({format_size(info['total_size_mb'] * 1024 * 1024)}). " f"Continue? [y/N]: ") if response.lower() not in ['y', 'yes']: print("āŒ Operation cancelled") return except Exception: pass try: clear_cache() print("āœ… Cache cleared successfully") except Exception as e: print(f"āŒ Error clearing cache: {e}") sys.exit(1) def cmd_config_show(args): """Show current configuration""" config = RequestConfig() print("āš™ļø YFinance Plus Configuration") print("=" * 40) print(f"Max Retries: {config.max_retries}") print(f"Base Delay: {config.base_delay}s") print(f"Max Delay: {config.max_delay}s") print(f"Jitter Enabled: {config.jitter}") print(f"Cache Enabled: {config.enable_cache}") print(f"Cache Directory: {config.cache_dir}") print(f"User Agents: {len(config.user_agents)} configured") def cmd_test_connection(args): """Test connection to Yahoo Finance""" print("šŸ”— Testing connection to Yahoo Finance...") try: # Import here to avoid circular imports from yfinance_plus import Ticker print(" Creating test ticker...") ticker = Ticker("AAPL") print(" Fetching basic info...") info = ticker.info company_name = info.get('longName', 'Unknown') print(" Fetching historical data...") hist = ticker.history(period="5d") print(f"āœ… Connection test successful!") print(f" Company: {company_name}") print(f" Historical data points: {len(hist)}") if ticker.enhanced_yf._cache: cache_info = ticker.get_cache_info() print(f" Cache status: {cache_info['file_count']} files") except Exception as e: print(f"āŒ Connection test failed: {e}") sys.exit(1) def cmd_benchmark(args): """Run performance benchmark""" print("šŸƒ Running performance benchmark...") try: import time from yfinance_plus import Ticker, download symbols = ["AAPL", "GOOGL", "MSFT", "TSLA", "NVDA"] # Test individual tickers print(f" Testing individual tickers ({len(symbols)} symbols)...") start_time = time.time() for symbol in symbols: ticker = Ticker(symbol) hist = ticker.history(period="1mo") print(f" {symbol}: {len(hist)} data points") individual_time = time.time() - start_time # Test bulk download print(f" Testing bulk download...") start_time = time.time() data = download(symbols, period="1mo") bulk_time = time.time() - start_time print(f"\nšŸ“Š Benchmark Results:") print(f" Individual requests: {individual_time:.2f}s") print(f" Bulk download: {bulk_time:.2f}s") print(f" Bulk speedup: {individual_time/bulk_time:.2f}x") print(f" Data shape: {data.shape}") except Exception as e: print(f"āŒ Benchmark failed: {e}") sys.exit(1) def main(): """Main CLI entry point""" parser = argparse.ArgumentParser( description="YFinance Plus CLI - Cache management and utilities", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: yfp cache info # Show cache information yfp cache clear # Clear cache (with confirmation) yfp cache clear --force # Clear cache without confirmation yfp config show # Show current configuration yfp test # Test connection to Yahoo Finance yfp benchmark # Run performance benchmark """ ) subparsers = parser.add_subparsers(dest='command', help='Available commands') # Cache commands cache_parser = subparsers.add_parser('cache', help='Cache management commands') cache_subparsers = cache_parser.add_subparsers(dest='cache_command') # Cache info info_parser = cache_subparsers.add_parser('info', help='Show cache information') info_parser.add_argument('--limit', type=int, help='Limit number of files to show') info_parser.set_defaults(func=cmd_cache_info) # Cache clear clear_parser = cache_subparsers.add_parser('clear', help='Clear cache') clear_parser.add_argument('--force', action='store_true', help='Clear without confirmation') clear_parser.set_defaults(func=cmd_cache_clear) # Config commands config_parser = subparsers.add_parser('config', help='Configuration commands') config_subparsers = config_parser.add_subparsers(dest='config_command') # Config show show_parser = config_subparsers.add_parser('show', help='Show current configuration') show_parser.set_defaults(func=cmd_config_show) # Test command test_parser = subparsers.add_parser('test', help='Test connection to Yahoo Finance') test_parser.set_defaults(func=cmd_test_connection) # Benchmark command benchmark_parser = subparsers.add_parser('benchmark', help='Run performance benchmark') benchmark_parser.set_defaults(func=cmd_benchmark) # Parse arguments args = parser.parse_args() # Handle no command if not args.command: parser.print_help() return # Handle cache subcommands if args.command == 'cache' and not args.cache_command: cache_parser.print_help() return # Handle config subcommands if args.command == 'config' and not args.config_command: config_parser.print_help() return # Execute command if hasattr(args, 'func'): try: args.func(args) except KeyboardInterrupt: print("\nāŒ Operation cancelled by user") sys.exit(1) else: parser.print_help() if __name__ == "__main__": main()