#!/usr/bin/env python3 """ Research Module - Deep research using Gemini CLI. Crawls articles, finds correlations, researches companies. Outputs research_report.md for later analysis. """ import argparse import json import os import shutil import subprocess import sys from datetime import datetime from pathlib import Path from utils import ensure_venv from fetch_news import PortfolioError, get_market_news, get_portfolio_news SCRIPT_DIR = Path(__file__).parent CONFIG_DIR = SCRIPT_DIR.parent / "config" OUTPUT_DIR = SCRIPT_DIR.parent / "research" ensure_venv() def format_market_data(market_data: dict) -> str: """Format market data for research prompt.""" lines = ["## Market Data\n"] for region, data in market_data.get('markets', {}).items(): lines.append(f"### {data['name']}") for symbol, idx in data.get('indices', {}).items(): if 'data' in idx and idx['data']: price = idx['data'].get('price', 'N/A') change_pct = idx['data'].get('change_percent', 0) emoji = '๐Ÿ“ˆ' if change_pct >= 0 else '๐Ÿ“‰' lines.append(f"- {idx['name']}: {price} ({change_pct:+.2f}%) {emoji}") lines.append("") return '\n'.join(lines) def format_headlines(headlines: list) -> str: """Format headlines for research prompt.""" lines = ["## Current Headlines\n"] for article in headlines[:20]: source = article.get('source', 'Unknown') title = article.get('title', '') link = article.get('link', '') lines.append(f"- [{source}] {title}") if link: lines.append(f" URL: {link}") return '\n'.join(lines) def format_portfolio_news(portfolio_data: dict) -> str: """Format portfolio news for research prompt.""" lines = ["## Portfolio Analysis\n"] for symbol, data in portfolio_data.get('stocks', {}).items(): quote = data.get('quote', {}) price = quote.get('price', 'N/A') change_pct = quote.get('change_percent', 0) lines.append(f"### {symbol} (${price}, {change_pct:+.2f}%)") for article in data.get('articles', [])[:5]: title = article.get('title', '') link = article.get('link', '') lines.append(f"- {title}") if link: lines.append(f" URL: {link}") lines.append("") return '\n'.join(lines) def gemini_available() -> bool: return shutil.which('gemini') is not None def research_with_gemini(content: str, focus_areas: list = None) -> str: """Perform deep research using Gemini CLI. Args: content: Combined market/headlines/portfolio content focus_areas: Optional list of focus areas (e.g., ['earnings', 'macro', 'sectors']) Returns: Research report text """ focus_prompt = "" if focus_areas: focus_prompt = f""" Focus areas for the research: {', '.join(f'- {area}' for area in focus_areas)} Go deep on each area. """ prompt = f"""You are an experienced investment research analyst. Your task is to deliver deep research on current market developments. {focus_prompt} Please analyze the following market data: {content} ## Analysis Requirements: 1. **Macro Trends**: What is driving the market today? Which economic data/decisions matter? 2. **Sector Analysis**: Which sectors are performing best/worst? Why? 3. **Company News**: Relevant earnings, M&A, product launches? 4. **Risks**: What downside risks should be noted? 5. **Opportunities**: Which positive developments offer opportunities? 6. **Correlations**: Are there links between different news items/asset classes? 7. **Trade Ideas**: Concrete setups based on the analysis (not financial advice!) 8. **Sources**: Original links for further research Be analytical, objective, and opinionated where appropriate. Deliver a substantial report (500-800 words). """ try: result = subprocess.run( ['gemini', prompt], capture_output=True, text=True, timeout=120 ) if result.returncode == 0: return result.stdout.strip() else: return f"โš ๏ธ Gemini research error: {result.stderr}" except subprocess.TimeoutExpired: return "โš ๏ธ Gemini research timeout" except FileNotFoundError: return "โš ๏ธ Gemini CLI not found. Install: brew install gemini-cli" def format_raw_data_report(market_data: dict, portfolio_data: dict) -> str: parts = [] if market_data: parts.append(format_market_data(market_data)) if market_data.get('headlines'): parts.append(format_headlines(market_data['headlines'])) if portfolio_data and 'error' not in portfolio_data: parts.append(format_portfolio_news(portfolio_data)) return '\n\n'.join(parts) def generate_research_content(market_data: dict, portfolio_data: dict, focus_areas: list = None) -> dict: raw_report = format_raw_data_report(market_data, portfolio_data) if not raw_report.strip(): return { 'report': '', 'source': 'none' } if gemini_available(): return { 'report': research_with_gemini(raw_report, focus_areas), 'source': 'gemini' } return { 'report': raw_report, 'source': 'raw' } def generate_research_report(args): """Generate full research report.""" OUTPUT_DIR.mkdir(parents=True, exist_ok=True) config_path = CONFIG_DIR / "config.json" if not config_path.exists(): print("โš ๏ธ No config found. Run 'finance-news wizard' first.", file=sys.stderr) sys.exit(1) # Fetch fresh data print("๐Ÿ“ก Fetching market data...", file=sys.stderr) # Get market overview market_data = get_market_news( args.limit if hasattr(args, 'limit') else 5, regions=args.regions.split(',') if hasattr(args, 'regions') else ["us", "europe"], max_indices_per_region=2 ) # Get portfolio news try: portfolio_data = get_portfolio_news( args.limit if hasattr(args, 'limit') else 5, args.max_stocks if hasattr(args, 'max_stocks') else 10 ) except PortfolioError as exc: print(f"โš ๏ธ Skipping portfolio: {exc}", file=sys.stderr) portfolio_data = None # Build report focus_areas = None if hasattr(args, 'focus') and args.focus: focus_areas = args.focus.split(',') research_result = generate_research_content(market_data, portfolio_data, focus_areas) research_report = research_result['report'] source = research_result['source'] if not research_report.strip(): print("โš ๏ธ No data available for research", file=sys.stderr) return if source == 'gemini': print("๐Ÿ”ฌ Running deep research with Gemini...", file=sys.stderr) else: print("๐Ÿงพ Gemini not available; using raw data report", file=sys.stderr) # Add metadata header timestamp = datetime.now().isoformat() date_str = datetime.now().strftime("%Y-%m-%d %H:%M") full_report = f"""# Market Research Report **Generiert:** {date_str} **Quelle:** Finance News Skill --- {research_report} --- *This report was generated automatically. Not financial advice.* """ # Save to file output_file = OUTPUT_DIR / f"research_{datetime.now().strftime('%Y-%m-%d')}.md" with open(output_file, 'w') as f: f.write(full_report) print(f"โœ… Research report saved to: {output_file}", file=sys.stderr) # Also output to stdout if args.json: print(json.dumps({ 'report': research_report, 'saved_to': str(output_file), 'timestamp': timestamp })) else: print("\n" + "="*60) print("RESEARCH REPORT") print("="*60) print(research_report) def main(): parser = argparse.ArgumentParser(description='Deep Market Research') parser.add_argument('--limit', type=int, default=5, help='Max headlines per source') parser.add_argument('--regions', default='us,europe', help='Comma-separated regions') parser.add_argument('--max-stocks', type=int, default=10, help='Max portfolio stocks') parser.add_argument('--focus', help='Focus areas (comma-separated)') parser.add_argument('--json', action='store_true', help='Output as JSON') args = parser.parse_args() generate_research_report(args) if __name__ == '__main__': main()