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.
305 lines
10 KiB
Python
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() |