Initial commit with translated description

This commit is contained in:
2026-03-29 10:21:46 +08:00
commit 18e90b0b09
67 changed files with 20609 additions and 0 deletions

283
scripts/research.py Normal file
View File

@@ -0,0 +1,283 @@
#!/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()