You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
yfinance_plus/yfinance_enhanced_cli.py

305 lines
10 KiB
Python

#!/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_enhanced import get_cache_info, clear_cache, HistoricalDataCache, RequestConfig
except ImportError:
print("Error: yfinance_enhanced 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_enhanced 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_enhanced 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()