mishrabp commited on
Commit
f1c303b
·
verified ·
1 Parent(s): 10d1678

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -1,94 +1,28 @@
1
  ---
2
- title: Stock Analyst
3
- emoji: 📈
4
- colorFrom: blue
5
- colorTo: green
6
  sdk: docker
7
- sdk_version: "0.0.1"
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
- short_description: Multi-Agent Stock Analysis Team (uses AutoGen)
12
  ---
13
 
14
- # Stock Analyst
15
 
16
- This is an experimental multi-agent system for comprehensive stock market analysis. It uses **Microsoft AutoGen** to orchestrate a team of specialized agents that collaborate to provide deep insights and investment decisions.
17
 
18
- ## Features
19
- - **Team of Agents**: Collaborative analysis from Trends, News, Sentiment, and Decision agents.
20
- - **Round-Robin Orchestration**: Agents take turns sharing insights in a structured conversation.
21
- - **Real-time Data**: Fetches live stock history and financial data via `yfinance`.
22
- - **News Integration**: Searches DuckDuckGo for the latest market news.
23
- - **Streamlit UI**: Clean, interactive interface with agent avatars and real-time streaming.
24
 
25
- ## Usage
26
- 1. Enter a valid stock ticker (e.g., TSLA, NVDA, AAPL).
27
- 2. Click **Analyze Stock**.
28
- 3. Watch the agents collaborate in real-time to analyze trends, news, and sentiment alongside the "Decision Agent" making the final call.
29
 
30
- ## Supported Tools
31
- - **Yahoo Finance**: Historical prices, analyst recommendations, market sentiment.
32
- - **DuckDuckGo**: Live news search.
33
- - **Web Scraping**: Fetching and summarizing full article content.
34
 
35
- ## Project Folder Structure
36
 
37
- ```
38
- stock-analyst/
39
- ├── app.py # Main Streamlit UI
40
- ├── aagents/
41
- │ ├── agents.py # Agent definitions (Trends, News, Sentiment, Decision) and factories
42
- ├── teams/
43
- │ ├── team.py # RoundRobinGroupChat orchestration logic
44
- ├── tools/
45
- │ ├── yf_tools.py # Yahoo Finance tool wrappers
46
- │ ├── search_tools.py # DuckDuckGo and web scraping tools
47
- ├── Dockerfile # Deployment configuration
48
- └── README.md # Project documentation
49
- ```
50
-
51
- ## Agents (`aagents/agents.py`)
52
-
53
- - **Stock Trends Agent** (📈):
54
- - Fetches historical price data.
55
- - Analyzes price movements and volume trends.
56
- - Outputs structured `StockTrend` data for UI visualization.
57
-
58
- - **News Agent** (📰):
59
- - Searches for top recent news stories.
60
- - Fetches and reads full article content.
61
- - Summarizes key events impacting the stock.
62
-
63
- - **Sentiment Agent** (💡):
64
- - Check general market sentiment.
65
- - Reviews analyst recommendations.
66
- - Aggregates expert opinions.
67
-
68
- - **Decision Agent** (⚖️):
69
- - Synthesizes all gathered information.
70
- - Provides a final "Invest" or "Not Invest" decision.
71
- - Summarizes the rationale.
72
-
73
- ## Key Technologies
74
-
75
- | Component | Technology | Purpose |
76
- |-----------|-----------|---------|
77
- | Agent Framework | Microsoft AutoGen | Multi-agent orchestration |
78
- | LLM | GPT-4o / Gemini | Intelligence engine for agents |
79
- | UI Framework | Streamlit | User interface |
80
- | Data Source | yfinance | Stock market data |
81
- | Search | DuckDuckGo | Real-time news |
82
-
83
- ## Running Locally
84
-
85
- ```bash
86
- # Install dependencies
87
- uv sync
88
-
89
- # Set environment variables in .env or shell
90
- export GOOGLE_API_KEY="your-gemini-key" # or OPENAI_API_KEY if using OpenAI
91
-
92
- # Run the Streamlit app (from the root)
93
- streamlit run src/stock-analyst/app.py
94
- ```
 
1
  ---
2
+ title: Midas
3
+ emoji: 🪙
4
+ colorFrom: indigo
5
+ colorTo: yellow
6
  sdk: docker
 
7
  app_file: app.py
8
  pinned: false
9
  license: mit
10
+ short_description: Multi-agent investment team for stock analysis & decisions
11
  ---
12
 
13
+ # Midas
14
 
15
+ Midas the king with the golden touch is a multi-agent investment analysis team. Four specialist agents evaluate a stock from every angle and converge on a final investment recommendation.
16
 
17
+ ## What it does
 
 
 
 
 
18
 
19
+ - **Trend Agent** — price history, moving averages, momentum signals
20
+ - **News Agent** latest headlines and sentiment for the ticker
21
+ - **Sentiment Agent** — quantified market mood from news and social signals
22
+ - **Decision Agent** synthesises all inputs into a Buy / Hold / Sell verdict
23
 
24
+ ## Stack
 
 
 
25
 
26
+ Streamlit · OpenAI Agents SDK · Yahoo Finance · Google Gemini / GPT-4o
27
 
28
+ > **Disclaimer:** For informational purposes only. Not financial advice.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pyproject.toml CHANGED
@@ -1,5 +1,5 @@
1
  [project]
2
- name = "agents"
3
  version = "0.1.0"
4
  description = "Agentic AI project"
5
  readme = "README.md"
@@ -12,128 +12,156 @@ dependencies = [
12
  "openai>=2.8.1",
13
  "openai-agents>=0.5.1",
14
  "anthropic>=0.49.0",
15
- "langchain-openai>=1.0.3",
16
  "langchain-anthropic>=1.1.0",
17
  "langchain_huggingface>=1.1.0",
18
  "langchain_ollama>=1.0.0",
19
  "langchain_google_genai>=3.0.3",
20
  "langchain_groq>=1.0.1",
21
-
22
-
23
  # =======================
24
  # LANGCHAIN / LANGGRAPH
25
  # =======================
26
  "langchain>=1.0.7",
27
  "langchain-community>=0.4.1",
28
- "langgraph>=1.0.3",
29
  "langgraph-checkpoint-sqlite>=3.0.0",
30
- "langsmith>=0.4.43",
31
- "langchain-text-splitters>=1.0.0",
32
  "langchain-chroma>=1.0.0",
33
  "html2text>=2025.4.15",
34
  "traceloop-sdk>=0.33.0",
35
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  # =======================
37
  # VECTOR DB / INDEXING
38
  # =======================
39
  "faiss-cpu>=1.13.0",
40
- "chromadb==1.3.5",
41
  "sentence-transformers>=5.1.2",
42
  "pymupdf",
43
- "pypdf>=6.3.0",
44
  "pypdf2>=3.0.1",
45
  "arxiv>=2.3.1",
46
  "wikipedia>=1.4.0",
47
-
48
  # =======================
49
  # AUTOGEN
50
  # =======================
51
- "autogen-agentchat>=0.7.5",
52
- "autogen-ext[grpc,mcp,ollama,openai]>=0.7.5",
53
  "asyncio",
54
-
55
  # =======================
56
  # MCP
57
  # =======================
58
  "mcp-server-fetch>=2025.1.17",
59
  "mcp[cli]>=1.21.2",
60
-
61
  # =======================
62
  # NETWORKING / UTILITIES
63
  # =======================
64
  "psutil>=7.0.0",
65
- "python-dotenv>=1.0.1",
66
- "requests>=2.32.3",
67
- "aiohttp>=3.8.5",
68
  "httpx>=0.28.1",
69
  "speedtest-cli>=2.1.3",
70
  "logfire",
71
  "google-search-results",
72
  "smithery>=0.4.4",
73
  "sendgrid",
74
-
75
  # =======================
76
  # WEB SCRAPING
77
  # =======================
78
  "playwright>=1.51.0",
79
  "beautifulsoup4>=4.12.3",
80
- "lxml>=5.3.1",
81
-
82
  # =======================
83
  # FINANCE / NLP
84
  # =======================
85
  "yfinance>=0.2.66",
86
  "textblob>=0.17.1",
87
  "polygon-api-client>=1.16.3",
88
-
89
  # =======================
90
  # VISUAL / UI / PDF
91
  # =======================
92
  "plotly>=6.5.0",
93
- "streamlit>=1.51.0",
94
  "reportlab>=4.4.5",
95
  "fastapi",
96
-
 
 
 
 
 
97
  # =======================
98
  # AUDIO / VIDEO
99
  # =======================
100
- "yt_dlp>=2025.11.12",
101
- "openai-whisper>=1.0.0",
102
-
 
103
  # =======================
104
  # MACHINE LEARNING
105
  # =======================
106
  "scikit-learn>=1.7.2",
107
- "huggingface_hub<=1.1.4",
108
  "datasets>=4.4.1",
109
-
110
  # =======================
111
  # IPYNB SUPPORT
112
  # =======================
113
  "ipykernel>=7.1.0",
114
-
115
  # =======================
116
  # TOOLS
117
  # =======================
118
  "ddgs>=9.9.2",
119
  "duckduckgo_search",
120
  "azure-identity>=1.25.1",
121
-
 
 
 
 
122
  # =======================
123
  # OBSERVABILITY
124
  # =======================
 
125
  "openinference-instrumentation-autogen>=0.1.0",
126
  "openinference-instrumentation-openai>=0.1.15",
127
  "opentelemetry-sdk>=1.20.0",
128
  "opentelemetry-exporter-otlp>=1.20.0",
129
  "opentelemetry-api>=1.20.0",
 
 
 
 
 
 
 
 
 
 
130
  ]
131
 
132
  [dependency-groups]
133
  dev = [
134
- "pytest>=8.3.3",
135
  "ipykernel>=7.1.0",
136
  "pytest-asyncio",
 
137
  ]
138
 
139
  # ============================================================
@@ -149,20 +177,16 @@ build-backend = "setuptools.build_meta"
149
  # PACKAGING & DISCOVERY
150
  # ============================================================
151
  # Tells setuptools where to find the source code.
152
- # This makes 'common' and 'src' importable when installed (pip install -e .).
153
  [tool.setuptools.packages.find]
154
- where = ["."] # Look in the project root
155
- include = ["common*", "src*"] # Treat 'common' and 'src' folders as packages
156
 
157
 
158
  # ============================================================
159
  # PYTEST SETTINGS
160
  # ============================================================
161
- # Configures the test runner to automatically find code.
162
  [tool.pytest.ini_options]
163
- # Adds 'src' and 'common' to the python path during tests.
164
- # This allows tests to import modules (e.g., 'import travel_agent')
165
- # just like the apps do locally, preventing ModuleNotFoundError.
166
- pythonpath = ["src", "common"]
167
  testpaths = ["tests"] # Only look for tests in the 'tests' directory
168
  addopts = "-q" # Run in quiet mode (less verbose output)
 
 
1
  [project]
2
+ name = "agenticai"
3
  version = "0.1.0"
4
  description = "Agentic AI project"
5
  readme = "README.md"
 
12
  "openai>=2.8.1",
13
  "openai-agents>=0.5.1",
14
  "anthropic>=0.49.0",
15
+ "langchain-openai>=1.1.14",
16
  "langchain-anthropic>=1.1.0",
17
  "langchain_huggingface>=1.1.0",
18
  "langchain_ollama>=1.0.0",
19
  "langchain_google_genai>=3.0.3",
20
  "langchain_groq>=1.0.1",
 
 
21
  # =======================
22
  # LANGCHAIN / LANGGRAPH
23
  # =======================
24
  "langchain>=1.0.7",
25
  "langchain-community>=0.4.1",
26
+ "langgraph>=1.0.10rc1",
27
  "langgraph-checkpoint-sqlite>=3.0.0",
28
+ "langsmith>=0.8.0",
29
+ "langchain-text-splitters>=1.1.2",
30
  "langchain-chroma>=1.0.0",
31
  "html2text>=2025.4.15",
32
  "traceloop-sdk>=0.33.0",
33
+ # ==================================
34
+ # LlamIndex
35
+ # ==================================
36
+ "llama-index>=0.14.15",
37
+ "llama-index-llms-google-genai>=0.8.7",
38
+ "llama-index-embeddings-google-genai>=0.3.2",
39
+ "google-generativeai>=0.8.6",
40
+ # =======================
41
+ # MICROSOFT AGENT FRAMEWORK
42
+ # =======================
43
+ #"agent-framework==1.0.0b251204",
44
+ #"agent-framework-azure-ai==1.0.0b251204",
45
+ #"azure-ai-projects",
46
+ #"azure-ai-agents",
47
+ #"azure-ai-agents>=1.2.0b5",
48
+ #"agent-framework-azure-ai",
49
  # =======================
50
  # VECTOR DB / INDEXING
51
  # =======================
52
  "faiss-cpu>=1.13.0",
53
+ "chromadb>=0.4.0",
54
  "sentence-transformers>=5.1.2",
55
  "pymupdf",
56
+ "pypdf>=6.10.2",
57
  "pypdf2>=3.0.1",
58
  "arxiv>=2.3.1",
59
  "wikipedia>=1.4.0",
 
60
  # =======================
61
  # AUTOGEN
62
  # =======================
63
+ "autogen-agentchat==0.7.5",
64
+ "autogen-ext[grpc,mcp,ollama,openai]==0.7.5",
65
  "asyncio",
66
+ "phidata>=2.0.0",
67
  # =======================
68
  # MCP
69
  # =======================
70
  "mcp-server-fetch>=2025.1.17",
71
  "mcp[cli]>=1.21.2",
 
72
  # =======================
73
  # NETWORKING / UTILITIES
74
  # =======================
75
  "psutil>=7.0.0",
76
+ "python-dotenv>=1.2.2",
77
+ "requests>=2.33.0",
78
+ "aiohttp>=3.14.0",
79
  "httpx>=0.28.1",
80
  "speedtest-cli>=2.1.3",
81
  "logfire",
82
  "google-search-results",
83
  "smithery>=0.4.4",
84
  "sendgrid",
 
85
  # =======================
86
  # WEB SCRAPING
87
  # =======================
88
  "playwright>=1.51.0",
89
  "beautifulsoup4>=4.12.3",
90
+ "lxml>=6.1.0",
 
91
  # =======================
92
  # FINANCE / NLP
93
  # =======================
94
  "yfinance>=0.2.66",
95
  "textblob>=0.17.1",
96
  "polygon-api-client>=1.16.3",
 
97
  # =======================
98
  # VISUAL / UI / PDF
99
  # =======================
100
  "plotly>=6.5.0",
101
+ "streamlit>=1.54.0",
102
  "reportlab>=4.4.5",
103
  "fastapi",
104
+ "Pillow",
105
+ "python-docx",
106
+ "matplotlib",
107
+ "fpdf",
108
+ "extra-streamlit-components",
109
+ "nest_asyncio",
110
  # =======================
111
  # AUDIO / VIDEO
112
  # =======================
113
+ "yt_dlp>=2026.2.21",
114
+ "openai-whisper==20250625",
115
+ "numba==0.63.1",
116
+ "llvmlite==0.46.0",
117
  # =======================
118
  # MACHINE LEARNING
119
  # =======================
120
  "scikit-learn>=1.7.2",
121
+ "huggingface_hub>=0.23.2",
122
  "datasets>=4.4.1",
 
123
  # =======================
124
  # IPYNB SUPPORT
125
  # =======================
126
  "ipykernel>=7.1.0",
 
127
  # =======================
128
  # TOOLS
129
  # =======================
130
  "ddgs>=9.9.2",
131
  "duckduckgo_search",
132
  "azure-identity>=1.25.1",
133
+ "azure-mgmt-resource>=23.0.1",
134
+ "azure-mgmt-compute>=30.3.0",
135
+ "azure-mgmt-monitor>=6.0.2",
136
+ "azure-monitor-query>=1.2.0",
137
+ "PyGithub>=2.1.1",
138
  # =======================
139
  # OBSERVABILITY
140
  # =======================
141
+ "langfuse>=3.0.0",
142
  "openinference-instrumentation-autogen>=0.1.0",
143
  "openinference-instrumentation-openai>=0.1.15",
144
  "opentelemetry-sdk>=1.20.0",
145
  "opentelemetry-exporter-otlp>=1.20.0",
146
  "opentelemetry-api>=1.20.0",
147
+ # =======================
148
+ # Google Authentication
149
+ # =======================
150
+ "google-auth>=2.22.0",
151
+ "google-auth-oauthlib>=0.4.6",
152
+ "google-auth-httplib2>=0.1.0",
153
+ "autoflake>=1.5.0",
154
+ "psycopg2-binary>=2.9.9",
155
+ "sqlalchemy>=2.0.46",
156
+ "llama-index-readers-database>=0.5.1",
157
  ]
158
 
159
  [dependency-groups]
160
  dev = [
161
+ "pytest>=9.0.3",
162
  "ipykernel>=7.1.0",
163
  "pytest-asyncio",
164
+ "pytest-cov>=7.1.0",
165
  ]
166
 
167
  # ============================================================
 
177
  # PACKAGING & DISCOVERY
178
  # ============================================================
179
  # Tells setuptools where to find the source code.
 
180
  [tool.setuptools.packages.find]
181
+ where = ["."]
182
+ include = ["src*"]
183
 
184
 
185
  # ============================================================
186
  # PYTEST SETTINGS
187
  # ============================================================
 
188
  [tool.pytest.ini_options]
189
+ pythonpath = ["src"]
 
 
 
190
  testpaths = ["tests"] # Only look for tests in the 'tests' directory
191
  addopts = "-q" # Run in quiet mode (less verbose output)
192
+ asyncio_mode = "auto" # Auto-detect async tests (requires pytest-asyncio)
run.py CHANGED
@@ -16,9 +16,10 @@ import sys
16
  import os
17
  import subprocess
18
  import argparse
 
19
  from pathlib import Path
20
  from typing import Dict, Optional
21
- from agents import Runner, SQLiteSession
22
  # from agents import set_trace_processors
23
  # from langsmith.wrappers import OpenAIAgentsTracingProcessor
24
 
@@ -28,46 +29,49 @@ load_dotenv(override=True)
28
 
29
  # App registry - maps app names to their paths and entry points
30
  APP_REGISTRY: Dict[str, Dict[str, str]] = {
31
- "healthcare": {
32
- "path": "src/healthcare-assistant",
33
  "entry": "app.py",
34
- "description": "Healthcare Assistant - Medical information with RAG and web search"
35
  },
36
  "deep-research": {
37
- "path": "src/deep-research",
 
 
 
 
 
38
  "entry": "app.py",
39
- "description": "Deep Research AI - Comprehensive research assistant"
40
  },
41
  "stock-analyst": {
42
- "path": "src/stock-analyst",
43
  "entry": "app.py",
44
- "description": "Stock Analyst - Financial analysis and stock recommendations"
45
  },
46
- "travel-agent": {
47
- "path": "src/travel-agent",
48
  "entry": "app.py",
49
- "description": "Travel Agent - Trip planning and travel recommendations"
50
  },
51
- "trip-planner": {
52
- "path": "src/trip-planner",
53
  "entry": "main.py",
54
- "description": "Trip Planner - Detailed trip itinerary planning"
 
55
  },
56
- "chatbot": {
57
- "path": "src/chatbot",
58
- "entry": "app.py",
59
- "description": "General Chatbot - Multi-purpose conversational AI"
 
60
  },
61
- "accessibility": {
62
- "path": "src/accessibility",
63
- "entry": "app.py",
64
- "description": "Accessibility Tools - Assistive technology applications"
 
65
  },
66
- "literature-review": {
67
- "path": "src/literature-review",
68
- "entry": "app.py",
69
- "description": "Literature Review Assistant - Multi-agent literature review tool"
70
- }
71
  }
72
 
73
 
@@ -142,33 +146,91 @@ def launch_app(app_name: str, port: Optional[int] = None):
142
  print(f"📂 Location: {config['path']}")
143
  print(f"🌐 Entry Point: {app_file}")
144
 
145
- # Build streamlit command
146
- cmd = ["streamlit", "run", app_file]
147
 
148
- # Add port if specified
149
- if port:
150
- cmd.extend(["--server.port", str(port)])
151
- print(f"🔌 Port: {port}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  else:
153
- print(f"🔌 Port: 8501 (default)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
  print("\n" + "=" * 70)
156
  print("\n🎯 Starting application...\n")
157
 
158
- # Prepare environment with project root in PYTHONPATH to fix imports
159
- env = os.environ.copy()
160
- env["PYTHONPATH"] = str(project_root) + os.pathsep + env.get("PYTHONPATH", "")
161
  print(f"\n\nPYTHONPATH: {env['PYTHONPATH']}")
162
 
163
  try:
164
  # Change to app directory and run
165
  os.chdir(app_dir)
166
- subprocess.run(cmd, env=env)
 
 
 
167
  except KeyboardInterrupt:
168
  print("\n\n👋 Application stopped by user")
169
  except FileNotFoundError:
170
- print("\n❌ Error: Streamlit not found. Please install it:")
171
- print(" pip install streamlit")
 
 
 
 
 
172
  sys.exit(1)
173
  except Exception as e:
174
  print(f"\n❌ Error launching app: {e}")
 
16
  import os
17
  import subprocess
18
  import argparse
19
+ import time
20
  from pathlib import Path
21
  from typing import Dict, Optional
22
+ # from agents import Runner, SQLiteSession
23
  # from agents import set_trace_processors
24
  # from langsmith.wrappers import OpenAIAgentsTracingProcessor
25
 
 
29
 
30
  # App registry - maps app names to their paths and entry points
31
  APP_REGISTRY: Dict[str, Dict[str, str]] = {
32
+ "research-assistant": {
33
+ "path": "src/ai-research-assistant",
34
  "entry": "app.py",
35
+ "description": "AI Research Assistant - Multi-specialist orchestrator for finance, news, and web research"
36
  },
37
  "deep-research": {
38
+ "path": "src/deep-research-reporter",
39
+ "entry": "app.py",
40
+ "description": "Deep Research Reporter - Plans, searches, and synthesises comprehensive research reports"
41
+ },
42
+ "healthcare": {
43
+ "path": "src/healthcare-rag-advisor",
44
  "entry": "app.py",
45
+ "description": "Healthcare RAG Advisor - Medical information retrieval using RAG and web search"
46
  },
47
  "stock-analyst": {
48
+ "path": "src/stock-investment-analyst",
49
  "entry": "app.py",
50
+ "description": "Stock Investment Analyst - Multi-agent investment team for technical and sentiment analysis"
51
  },
52
+ "travel-planner": {
53
+ "path": "src/travel-planner",
54
  "entry": "app.py",
55
+ "description": "Travel Planner - AI-powered trip planning with flight, hotel, and itinerary recommendations"
56
  },
57
+ "trip-planner-api": {
58
+ "path": "src/trip-planner-api",
59
  "entry": "main.py",
60
+ "type": "fastapi",
61
+ "description": "Trip Planner API - Phidata-powered trip itinerary planning REST API"
62
  },
63
+ "market-analyst": {
64
+ "path": "src/market-analyst",
65
+ "entry": "backend/main.py",
66
+ "type": "fastapi",
67
+ "description": "AI Market Analyst - Real-time multi-agent market analysis with streaming (Vue.js + FastAPI)"
68
  },
69
+ "test": {
70
+ "path": ".",
71
+ "entry": "tests",
72
+ "type": "test",
73
+ "description": "Run Project Tests - Executes pytest suite"
74
  },
 
 
 
 
 
75
  }
76
 
77
 
 
146
  print(f"📂 Location: {config['path']}")
147
  print(f"🌐 Entry Point: {app_file}")
148
 
149
+ app_type = config.get("type", "streamlit")
 
150
 
151
+ python_exe = sys.executable
152
+ is_windows = sys.platform == "win32"
153
+
154
+ # Prepare environment with project root in PYTHONPATH to fix imports
155
+ env = os.environ.copy()
156
+ env["PYTHONPATH"] = str(project_root) + os.pathsep + env.get("PYTHONPATH", "")
157
+
158
+ # Decoupled App Logic: Build frontend if needed
159
+ if app_name == "market-analyst":
160
+ frontend_dir = project_root / "src/market-analyst/frontend"
161
+ dist_dir = frontend_dir / "dist"
162
+ if not dist_dir.exists():
163
+ print("\n🛠️ Frontend build missing. Building now...")
164
+ subprocess.run(["npm", "run", "build"], cwd=frontend_dir, shell=is_windows)
165
+ print("✅ Frontend built.\n")
166
+
167
+ # App Type specific logic
168
+ if app_type == "fastapi":
169
+ # Extract module name from entry point (e.g. backend/main.py -> backend.main)
170
+ module_path = app_file.replace(".py", "").replace("/", ".").replace("\\", ".")
171
+ cmd = [python_exe, "-m", "uvicorn", f"{module_path}:app", "--host", "0.0.0.0"]
172
+ default_port = 8000
173
+ elif app_type == "script":
174
+ cmd = [python_exe, app_file]
175
+ default_port = None
176
+ elif app_type == "test":
177
+ cmd = [python_exe, "-m", "pytest", app_file, "-v"]
178
+ default_port = None
179
+ elif app_type == "npm":
180
+ cmd = ["npm", "run", "dev"]
181
+ default_port = 5173
182
  else:
183
+ cmd = [python_exe, "-m", "streamlit", "run", app_file]
184
+ default_port = 8501
185
+
186
+ # Add port if specified
187
+ actual_port = port if port else default_port
188
+
189
+ if app_type in ["streamlit", "fastapi", "npm"]:
190
+ if port:
191
+ if app_type == "fastapi":
192
+ cmd.extend(["--port", str(port)])
193
+ elif app_type == "npm":
194
+ cmd.extend(["--", "--port", str(port)])
195
+ else:
196
+ cmd.extend(["--server.port", str(port)])
197
+ print(f"🔌 Port: {port}")
198
+ else:
199
+ print(f"🔌 Port: {default_port} (default)")
200
+
201
+ # Kill any process using the target port (Port is only relevant for web apps)
202
+ try:
203
+ import platform
204
+ if platform.system() != "Windows":
205
+ # Use fuser on Linux/Mac to kill processes on the port
206
+ kill_cmd = ["fuser", "-k", f"{actual_port}/tcp"]
207
+ subprocess.run(kill_cmd, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
208
+ print(f"🧹 Cleaned up port {actual_port}")
209
+ except Exception:
210
+ pass # Silently continue if cleanup fails
211
 
212
  print("\n" + "=" * 70)
213
  print("\n🎯 Starting application...\n")
214
 
 
 
 
215
  print(f"\n\nPYTHONPATH: {env['PYTHONPATH']}")
216
 
217
  try:
218
  # Change to app directory and run
219
  os.chdir(app_dir)
220
+
221
+
222
+
223
+ subprocess.run(cmd, env=env, shell=is_windows)
224
  except KeyboardInterrupt:
225
  print("\n\n👋 Application stopped by user")
226
  except FileNotFoundError:
227
+ binary = "command"
228
+ if app_type == "fastapi": binary = "uvicorn"
229
+ elif app_type == "streamlit": binary = "streamlit"
230
+ elif app_type == "test": binary = "pytest"
231
+
232
+ print(f"\n❌ Error: {binary} not found in the current environment.")
233
+ print(f" Please install it: pip install {binary}")
234
  sys.exit(1)
235
  except Exception as e:
236
  print(f"\n❌ Error launching app: {e}")
src/stock-investment-analyst/Dockerfile ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ ENV PYTHONUNBUFFERED=1 \
4
+ DEBIAN_FRONTEND=noninteractive \
5
+ PYTHONPATH=/app/src/stock-analyst:$PYTHONPATH
6
+
7
+ WORKDIR /app
8
+
9
+ # System deps
10
+ RUN apt-get update && apt-get install -y \
11
+ git build-essential curl \
12
+ && rm -rf /var/lib/apt/lists/*
13
+
14
+ # Install uv
15
+ RUN curl -LsSf https://astral.sh/uv/install.sh | sh
16
+ ENV PATH="/root/.local/bin:$PATH"
17
+
18
+ # Copy project metadata
19
+ COPY pyproject.toml .
20
+ COPY uv.lock .
21
+
22
+ # Copy application code
23
+ COPY common/ ./common/
24
+ COPY src/stock-analyst/ ./src/stock-analyst/
25
+
26
+ # Install dependencies using uv, then export and install with pip to system
27
+ # We use --no-dev to exclude dev dependencies if any
28
+ RUN uv sync --frozen --no-dev && \
29
+ uv pip install -e . --system
30
+
31
+ # Copy entry point
32
+ COPY run.py .
33
+
34
+ EXPOSE 7860
35
+
36
+ CMD ["python", "run.py", "stock-analyst", "--port", "7860"]
src/stock-investment-analyst/README.md ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Midas
3
+ emoji: 🪙
4
+ colorFrom: indigo
5
+ colorTo: yellow
6
+ sdk: docker
7
+ app_file: app.py
8
+ pinned: false
9
+ license: mit
10
+ short_description: Multi-agent investment team for stock analysis & decisions
11
+ ---
12
+
13
+ # Midas
14
+
15
+ Midas — the king with the golden touch — is a multi-agent investment analysis team. Four specialist agents evaluate a stock from every angle and converge on a final investment recommendation.
16
+
17
+ ## What it does
18
+
19
+ - **Trend Agent** — price history, moving averages, momentum signals
20
+ - **News Agent** — latest headlines and sentiment for the ticker
21
+ - **Sentiment Agent** — quantified market mood from news and social signals
22
+ - **Decision Agent** — synthesises all inputs into a Buy / Hold / Sell verdict
23
+
24
+ ## Stack
25
+
26
+ Streamlit · OpenAI Agents SDK · Yahoo Finance · Google Gemini / GPT-4o
27
+
28
+ > **Disclaimer:** For informational purposes only. Not financial advice.
src/stock-investment-analyst/aagents/__init__.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .stock_trends_agent import get_stock_trends_agent
2
+ from .news_agent import get_news_agent
3
+ from .sentiment_agent import get_sentiment_agent
4
+ from .decision_agent import get_decision_agent
5
+ from pydantic import BaseModel
6
+
7
+ class StockTrend(BaseModel):
8
+ stock_name: str
9
+ trade_date: str
10
+ open_price: float
11
+ close_price: float
12
+ high_price: float
13
+ low_price: float
14
+ volume: int
15
+
16
+ __all__ = ["get_stock_trends_agent", "get_news_agent", "get_sentiment_agent", "get_decision_agent", "StockTrend"]
src/stock-investment-analyst/aagents/decision_agent.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from autogen_agentchat.agents import AssistantAgent
2
+ from core.model import get_model_client
3
+
4
+ def get_decision_agent():
5
+ # Upgrade to Pro model for better reasoning
6
+ # Using 1.5-pro as requested for better decision making
7
+ model_client = get_model_client()
8
+
9
+ decision_agent = AssistantAgent(
10
+ name="decision_agent",
11
+ model_client=model_client,
12
+ tools=[], # Decision agent usually synthesizes info, might not need tools if it consumes chat history
13
+ system_message=(
14
+ "You are the Decision Agent. Your role is to synthesize data from the Stock Trends, News, and Sentiment agents "
15
+ "to provide a final, well-reasoned investment recommendation.\n\n"
16
+
17
+ "**STEP 1: CALCULATE WEIGHTED SCORE**\n"
18
+ "You MUST score the stock on the following criteria (0-10 scale) and calculate the weighted total:\n"
19
+ "1. **Technical Indicators (Weight: 40%)**: Score 0-10 based on trend direction, moving averages, and volume.\n"
20
+ "2. **News Sentiment (Weight: 30%)**: Score 0-10 based on recent headlines and PR tone.\n"
21
+ "3. **Analyst Ratings (Weight: 30%)**: Score 0-10 based on analyst consensus and price targets.\n\n"
22
+
23
+ "**Formula**: `(Technical * 0.4) + (News * 0.3) + (Analyst * 0.3) = Total Score`\n\n"
24
+
25
+ "**STEP 2: DETERMINE DECISION**\n"
26
+ "- If **Total Score > 7.5** -> Decision: **INVEST**\n"
27
+ "- If **Total Score < 5.0** -> Decision: **AVOID**\n"
28
+ "- Else -> Decision: **WAIT**\n\n"
29
+
30
+ "**Output Requirement:**\n"
31
+ "You MUST provide your response in the following structured format:\n"
32
+ "1. **Decision**: [Invest / Wait / Avoid] (Based strictly on the rule above)\n"
33
+ "2. **Scoring Table**:\n"
34
+ " | Category | Score (0-10) | Weighted Score |\n"
35
+ " | :--- | :--- | :--- |\n"
36
+ " | Technicals (40%) | [Score] | [Val] |\n"
37
+ " | News (30%) | [Score] | [Val] |\n"
38
+ " | Analysts (30%) | [Score] | [Val] |\n"
39
+ " | **TOTAL** | | **[Total Score]** |\n"
40
+ "3. **Risk Level**: [Low / Medium / High]\n"
41
+ "4. **Reasoning**:\n"
42
+ " - **Pros**: [List top 3 positive factors]\n"
43
+ " - **Cons**: [List top 3 negative factors]\n"
44
+ "5. **Validation**: Briefly explain why the confidence score was chosen based on the consistency of the data.\n\n"
45
+
46
+ "Also provide the current stock price if available in the context.\n"
47
+ "End your response with 'Decision Made' once you finalize the decision."
48
+ )
49
+ )
50
+ return decision_agent
src/stock-investment-analyst/aagents/news_agent.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from autogen_agentchat.agents import AssistantAgent
2
+ from autogen_core.tools import FunctionTool
3
+ from tools.search_tools import _duckduckgo_search, searchQuery
4
+ from core.model import get_model_client
5
+
6
+ def get_news_agent():
7
+ model_client = get_model_client()
8
+
9
+ async def news_search(query: str) -> str:
10
+ """
11
+ Search for latest news regarding a topic or stock.
12
+ """
13
+ # Anchor search to reputable sources as per Suggestion 3
14
+ # reputable_sources = " (site:bloomberg.com OR site:reuters.com OR site:cnbc.com OR site:wsj.com OR site:finance.yahoo.com)"
15
+ # if "site:" not in query:
16
+ # query += reputable_sources
17
+
18
+ # Use underlying function with proper params
19
+ params = searchQuery(query=query, search_type="news", timelimit="w", max_results=5)
20
+ # _duckduckgo_search returns list[dict], convert to str
21
+ results = _duckduckgo_search(params)
22
+ return str(results)
23
+
24
+ news_tool = FunctionTool(news_search, description="Search for latest top 5 news for a given stock or topic. Returns headlines and snippets only.")
25
+
26
+ news_agent = AssistantAgent(
27
+ name="news_agent",
28
+ model_client=model_client,
29
+ tools=[news_tool],
30
+ system_message=(
31
+ "You are the News Agent. "
32
+ "1. Search for the latest top 5 news stories related to the given stock using `news_tool`. "
33
+ "2. Prioritize reputable result sources like Bloomberg, Reuters, CNBC, WSJ, and Yahoo Finance if possible. "
34
+ "3. Summarize the key insights from the news stories. "
35
+ "Do NOT provide any final investment decision."
36
+ )
37
+ )
38
+ return news_agent
src/stock-investment-analyst/aagents/sentiment_agent.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from autogen_agentchat.agents import AssistantAgent
2
+ from autogen_core.tools import FunctionTool
3
+ from tools.yf_tools import _get_analyst_recommendations
4
+ from core.model import get_model_client
5
+
6
+ def get_sentiment_agent():
7
+ model_client = get_model_client()
8
+
9
+ async def get_market_sentiment(symbol: str, period: str) -> str:
10
+ """Get market sentiment for a stock."""
11
+ from tools.yf_tools import _get_market_sentiment
12
+ return _get_market_sentiment(symbol, period)
13
+
14
+ async def get_analyst_recs(symbol: str) -> str:
15
+ """Get analyst recommendations for a stock."""
16
+ # _get_analyst_recommendations is already imported
17
+ return _get_analyst_recommendations(symbol)
18
+
19
+ sentiment_tool = FunctionTool(get_market_sentiment, description="Get market sentiment")
20
+ analyst_tool = FunctionTool(get_analyst_recs, description="Get analyst recommendations")
21
+
22
+ sentiment_agent = AssistantAgent(
23
+ name="sentiment_agent",
24
+ model_client=model_client,
25
+ tools=[sentiment_tool, analyst_tool],
26
+ system_message=(
27
+ "You are the Market Sentiment Agent. "
28
+ "You gather overall market sentiment, relevant analyst reports, and expert opinions. "
29
+ "Do NOT provide any final investment decision."
30
+ )
31
+ )
32
+ return sentiment_agent
src/stock-investment-analyst/aagents/stock_trends_agent.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from autogen_agentchat.agents import AssistantAgent
2
+ from autogen_core.tools import FunctionTool
3
+ from tools.yf_tools import _get_history
4
+ from core.model import get_model_client
5
+
6
+ def get_stock_trends_agent():
7
+ model_client = get_model_client()
8
+
9
+ async def fetch_stock_history(symbol: str, period: str) -> str:
10
+ """
11
+ Gets real-time stock prices and changes over the last few months for the given stock name.
12
+
13
+ Args:
14
+ symbol: The stock ticker symbol (e.g., 'TSLA', 'AAPL').
15
+ period: The period to fetch data for (e.g., '1mo', '3mo').
16
+
17
+ Returns:
18
+ str: A formatted string showing the historical prices.
19
+ """
20
+ return _get_history(symbol, period)
21
+
22
+ get_history_tool = FunctionTool(fetch_stock_history, description="Gets real-time stock prices, changes over the last few months for 'stock_name'", strict=True)
23
+
24
+ stock_trends_agent_assistant = AssistantAgent(
25
+ name="stock_trends_agent",
26
+ model_client=model_client,
27
+ tools=[get_history_tool],
28
+ system_message=(
29
+ "You are the Stock Price Trends Agent practicing in India and USA stock markets. "
30
+ "You fetch and summarize stock prices, changes over the last 3 months, and general market trends. "
31
+ "Do NOT provide any final investment decision."
32
+ ),
33
+ )
34
+ return stock_trends_agent_assistant
src/stock-investment-analyst/app.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import asyncio
3
+ import os
4
+
5
+
6
+
7
+
8
+ from teams.investment_team import get_investment_team
9
+
10
+ st.set_page_config(page_title="Stock Investment Analyst", layout="wide", page_icon="📈")
11
+
12
+ # ------------------------------------------------------------------------------
13
+ # Custom CSS for layout improvements
14
+ # ------------------------------------------------------------------------------
15
+ st.markdown("""
16
+ <style>
17
+ .block-container {
18
+ padding-top: 1.5rem;
19
+ padding-bottom: 3rem;
20
+ }
21
+ </style>
22
+ """, unsafe_allow_html=True)
23
+
24
+ # ------------------------------------------------------------------------------
25
+ # Main Content
26
+ # ------------------------------------------------------------------------------
27
+ st.title("📈 Stock Investment Analyst")
28
+
29
+ # IMPORTANT DISCLAIMER
30
+ st.warning(
31
+ "**⚠️ DISCLAIMER: EDUCATIONAL PURPOSE ONLY**\n\n"
32
+ "This tool is an AI-powered experiment designed for educational and demonstration purposes. "
33
+ "The analysis provided is generated by artificial intelligence and **DOES NOT** constitute financial, investment, or legal advice. "
34
+ "Do not use this tool to make real-world financial decisions. Always consult with a qualified financial advisor."
35
+ )
36
+
37
+ st.markdown("#### Get a comprehensive analysis from our AI team of agents.")
38
+
39
+ # Input section with better spacing
40
+ st.divider()
41
+ with st.container():
42
+ col1, col2 = st.columns([3, 1], gap="medium")
43
+ with col1:
44
+ stock_name = st.text_input(
45
+ "Enter Stock Ticker",
46
+ value="Tesla",
47
+ placeholder="e.g. NVDA, TSLA, AAPL",
48
+ help="Enter the ticker symbol of the company you want to analyze."
49
+ )
50
+ with col2:
51
+ # Align button with input box
52
+ st.write("") # Spacer
53
+ st.write("") # Spacer
54
+ analyze_btn = st.button("🔍 Analyze Stock", type="primary", use_container_width=True)
55
+
56
+
57
+ async def run_analysis(ticker):
58
+ # Start a span for the analysis task.
59
+ # This becomes the parent for all subsequent spans (like OpenAI calls).
60
+ # with tracer.start_as_current_span("run_analysis") as span:
61
+ # span.set_attribute("stock.ticker", ticker) # Add useful metadata
62
+
63
+ task = f"Analyze stock trends, news, and sentiment for {ticker}, plus analyst reports and expert opinions, and then decide whether to invest."
64
+
65
+ st.markdown(f"### Analysis for **{ticker}**")
66
+
67
+ # Container for live updates
68
+ chat_container = st.container()
69
+
70
+ try:
71
+ # Run the team stream
72
+ investment_team = get_investment_team()
73
+ stream = investment_team.run_stream(task=task)
74
+
75
+ # Define icons for each agent
76
+ AGENT_ICONS = {
77
+ "stock_trends_agent": "📈",
78
+ "news_agent": "📰",
79
+ "sentiment_agent": "💡",
80
+ "decision_agent": "⚖️",
81
+ "user": "👤",
82
+ "System": "⚙️"
83
+ }
84
+
85
+ async for message in stream:
86
+ # Check if message has source and content attributes typical of agent messages
87
+ source = getattr(message, 'source', 'System')
88
+
89
+ with chat_container:
90
+ if 'TaskResult' in message.__class__.__name__:
91
+ if hasattr(message, 'stop_reason') and message.stop_reason:
92
+ st.info(f"Analysis Completed: {message.stop_reason}")
93
+ continue
94
+
95
+ # Use the icon mapping, default to None (Streamlit default) if not found
96
+ avatar = AGENT_ICONS.get(source, None)
97
+
98
+ with st.chat_message(source, avatar=avatar):
99
+ # Handle Tool Call events specifically to make them look like system logs
100
+ if 'ToolCall' in message.__class__.__name__:
101
+ with st.expander(f"⚙️ Tool Usage: {source}", expanded=False):
102
+ st.write(message)
103
+ continue
104
+
105
+ content = getattr(message, 'content', "")
106
+ st.write(content)
107
+
108
+ except Exception as e:
109
+ # Record the exception in the span if something crashes
110
+ # span.record_exception(e)
111
+ # span.set_status(trace.Status(trace.StatusCode.ERROR))
112
+ st.error(f"An error occurred during analysis: {e}")
113
+
114
+ if analyze_btn:
115
+ if stock_name:
116
+ with st.spinner(f"Gathering data and analyzing {stock_name}..."):
117
+ # Create a new event loop for this run if needed, or simply run
118
+ asyncio.run(run_analysis(stock_name))
119
+ else:
120
+ st.warning("Please enter a valid stock ticker.")
src/stock-investment-analyst/core/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+
2
+ from .model import get_model_client
3
+
4
+ __all__ = ["get_model_client"]
src/stock-investment-analyst/core/model.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from utility.autogen_model_factory import AutoGenModelFactory
2
+
3
+ def get_model_client(provider:str = "google"):
4
+ if provider.lower() == "google":
5
+ return AutoGenModelFactory.get_model(
6
+ provider="google",
7
+ model_name="gemini-3-pro-preview",
8
+ temperature=0,
9
+ model_info={
10
+ "family": "gemini",
11
+ "vision": True,
12
+ "function_calling": True,
13
+ "json_output": True,
14
+ "structured_output": True,
15
+ },
16
+ )
17
+ elif provider.lower() == "openai":
18
+ return AutoGenModelFactory.get_model(
19
+ provider="openai",
20
+ model_name="gpt-4o-mini",
21
+ temperature=0,
22
+ model_info={
23
+ "family": "gpt",
24
+ "vision": True,
25
+ "function_calling": True,
26
+ "json_output": True,
27
+ "structured_output": True,
28
+ },
29
+ )
30
+ else:
31
+ raise ValueError(f"Unsupported provider: {provider}")
32
+
src/stock-investment-analyst/teams/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .investment_team import get_investment_team
2
+
3
+ __all__ = ["get_investment_team"]
src/stock-investment-analyst/teams/investment_team.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from autogen_agentchat.conditions import MaxMessageTermination, TextMentionTermination
2
+ from autogen_agentchat.teams import SelectorGroupChat
3
+ from autogen_ext.models.openai import OpenAIChatCompletionClient
4
+ from autogen_agentchat.ui import Console
5
+ import os
6
+
7
+ from aagents import get_stock_trends_agent, get_news_agent, get_sentiment_agent, get_decision_agent, StockTrend
8
+ from autogen_agentchat.messages import StructuredMessage
9
+ from core.model import get_model_client
10
+
11
+ def get_investment_team():
12
+ # Try to register the message type to avoid "not registered" errors in GroupChat
13
+ try:
14
+ from autogen_core import TypeSubscription
15
+ pass
16
+ except ImportError:
17
+ pass
18
+
19
+ text_termination = TextMentionTermination("Decision Made")
20
+ max_message_termination = MaxMessageTermination(20)
21
+ termination = text_termination | max_message_termination
22
+
23
+ # # Model for the selector/moderator
24
+ # selector_model = OpenAIChatCompletionClient(
25
+ # model="gemini-2.5-flash",
26
+ # api_key=os.getenv("GOOGLE_API_KEY"),
27
+ # model_info={
28
+ # "family": "gemini",
29
+ # "vision": True,
30
+ # "function_calling": True,
31
+ # "json_output": True,
32
+ # "structured_output": True,
33
+ # },
34
+ # temperature=0
35
+ # )
36
+
37
+ # Selector Group Chat which allows dynamic speaker selection
38
+ investment_team = SelectorGroupChat(
39
+ [
40
+ get_stock_trends_agent(),
41
+ get_news_agent(),
42
+ get_sentiment_agent(),
43
+ get_decision_agent(),
44
+ ],
45
+ model_client=get_model_client(),
46
+ termination_condition=termination
47
+ )
48
+ return investment_team
49
+
src/stock-investment-analyst/tools/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from .search_tools import _duckduckgo_search, searchQuery
2
+ from .yf_tools import _get_history, _get_analyst_recommendations
3
+
4
+ __all__ = ["_duckduckgo_search", "searchQuery", "_get_history", "_get_analyst_recommendations"]
src/stock-investment-analyst/tools/search_tools.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ from ddgs import DDGS
3
+ from agents import function_tool
4
+
5
+ from pydantic import BaseModel, Field
6
+ from bs4 import BeautifulSoup
7
+ from typing import Optional
8
+
9
+
10
+
11
+ # ---------------------------------------------------------
12
+ # Load environment variables
13
+ # ---------------------------------------------------------
14
+
15
+
16
+ # ---------------------- MODELS ---------------------------
17
+ class searchQuery(BaseModel):
18
+ query: str = Field(..., description="The search query string.")
19
+ max_results: int = Field(5, description="The maximum number of search results to return.")
20
+ search_type: str = Field(
21
+ "text",
22
+ description="Search type: 'text' (default) or 'news'. Use 'news' to get publication dates."
23
+ )
24
+ timelimit: str = Field(
25
+ 'd',
26
+ description="Time limit for search results: 'd' (day), 'w' (week), 'm' (month), 'y' (year)."
27
+ )
28
+ region: str = Field("us-en", description="Region for search results (e.g., 'us-en').")
29
+
30
+
31
+ class searchResult(BaseModel):
32
+ title: str
33
+ link: str
34
+ snippet: str
35
+ datetime: Optional[str] = None
36
+
37
+
38
+ # ---------------------- PAGE FETCH TOOL ---------------------------
39
+ def _fetch_page_content(url: str, timeout: int = 3) -> Optional[str]:
40
+ """Fetch and extract text content from a web page."""
41
+ print(f"[DEBUG] fetch_page_content called with: {url} - timeout: {timeout}")
42
+ try:
43
+ headers = {
44
+ 'User-Agent': (
45
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
46
+ 'AppleWebKit/537.36 (KHTML, like Gecko) '
47
+ 'Chrome/91.0.4472.124 Safari/537.36'
48
+ )
49
+ }
50
+ response = requests.get(url, headers=headers, timeout=timeout)
51
+ response.raise_for_status()
52
+
53
+ soup = BeautifulSoup(response.content, 'html.parser')
54
+
55
+ # Remove irrelevant elements
56
+ for tag in soup(["script", "style", "nav", "footer", "header"]):
57
+ tag.decompose()
58
+
59
+ # Extract text
60
+ text = soup.get_text(separator='\n', strip=True)
61
+
62
+ # Clean whitespace
63
+ lines = (line.strip() for line in text.splitlines())
64
+ chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
65
+ text = '\n'.join(chunk for chunk in chunks if chunk)
66
+
67
+ return text
68
+ except Exception as e:
69
+ print(f"[WARNING] Failed to fetch content from {url}: {str(e)}")
70
+ return None
71
+
72
+
73
+ @function_tool
74
+ def fetch_page_content(url: str, timeout: int = 3) -> Optional[str]:
75
+ """Fetch and extract text content from a web page."""
76
+ return _fetch_page_content(url, timeout)
77
+
78
+
79
+ # ---------------------- SEARCH TOOL ---------------------------
80
+ def _duckduckgo_search(params: searchQuery) -> list[dict]:
81
+ """Perform a DuckDuckGo search and return only snippets.
82
+ No page content fetched here."""
83
+ print(f"[DEBUG] duckduckgo_search called with: {params}")
84
+
85
+ results = []
86
+ with DDGS() as ddgs:
87
+ if params.search_type == "news":
88
+ search_results = ddgs.news(
89
+ params.query,
90
+ max_results=params.max_results,
91
+ timelimit=params.timelimit,
92
+ region=params.region
93
+ )
94
+ for result in search_results:
95
+ results.append(
96
+ searchResult(
97
+ title=result.get("title", ""),
98
+ link=result.get("url", ""),
99
+ snippet=result.get("body", ""),
100
+ datetime=result.get("date", "")
101
+ ).model_dump()
102
+ )
103
+ else:
104
+ search_results = ddgs.text(
105
+ params.query,
106
+ max_results=params.max_results,
107
+ timelimit=params.timelimit,
108
+ region=params.region
109
+ )
110
+ for result in search_results:
111
+ results.append(
112
+ searchResult(
113
+ title=result.get("title", ""),
114
+ link=result.get("href", ""),
115
+ snippet=result.get("body", "")
116
+ ).model_dump()
117
+ )
118
+
119
+ print(f"[DEBUG] duckduckgo_search returning {len(results)} results")
120
+ return results
121
+
122
+ @function_tool
123
+ def duckduckgo_search(params: searchQuery) -> list[dict]:
124
+ """Perform a DuckDuckGo search and return only snippets.
125
+ No page content fetched here."""
126
+ return _duckduckgo_search(params)
127
+
src/stock-investment-analyst/tools/yf_tools.py ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+ import yfinance as yf
4
+
5
+ from agents import function_tool
6
+ from datetime import datetime, timedelta
7
+
8
+
9
+
10
+ # Load environment variables
11
+
12
+
13
+
14
+ # ============================================================
15
+ # 🔹 YAHOO FINANCE TOOLSET
16
+ # ============================================================
17
+ def _get_summary(symbol: str, period: str = "1d", interval: str = "1h") -> str:
18
+ print(f"[DEBUG] get_summary called for symbol='{symbol}', period='{period}', interval='{interval}'")
19
+ try:
20
+ ticker = yf.Ticker(symbol)
21
+
22
+ # Calculate start and end dates based on period
23
+ end_date = datetime.today()
24
+ if period.endswith("d"):
25
+ days = int(period[:-1])
26
+ elif period.endswith("mo"):
27
+ days = int(period[:-2]) * 30
28
+ elif period.endswith("y"):
29
+ days = int(period[:-1]) * 365
30
+ else:
31
+ days = 30 # default 1 month
32
+ start_date = end_date - timedelta(days=days)
33
+
34
+ # Fetch recent data explicitly
35
+ data = ticker.history(
36
+ start=start_date.strftime("%Y-%m-%d"),
37
+ end=end_date.strftime("%Y-%m-%d"),
38
+ interval=interval
39
+ )
40
+
41
+ if data.empty:
42
+ return f"No data found for symbol '{symbol}'."
43
+
44
+ latest = data.iloc[-1]
45
+ current_price = round(latest["Close"], 2)
46
+ open_price = round(latest["Open"], 2)
47
+ change = round(current_price - open_price, 2)
48
+ pct_change = round((change / open_price) * 100, 2)
49
+
50
+ info = ticker.info
51
+ long_name = info.get("longName", symbol)
52
+ currency = info.get("currency", "USD")
53
+
54
+ formatted = [
55
+ f"📈 {long_name} ({symbol})",
56
+ f"Current Price: {current_price} {currency}",
57
+ f"Change: {change} ({pct_change}%)",
58
+ f"Open: {open_price} | High: {round(latest['High'], 2)} | Low: {round(latest['Low'], 2)}",
59
+ f"Volume: {int(latest['Volume'])}",
60
+ f"Period: {period} | Interval: {interval}",
61
+ ]
62
+ return "\n".join(formatted)
63
+
64
+ except Exception as e:
65
+ return f"Error fetching data for '{symbol}': {e}"
66
+
67
+ def _get_market_sentiment(symbol: str, period: str = "1mo") -> str:
68
+ print(f"[DEBUG] get_market_sentiment called for symbol='{symbol}', period='{period}'")
69
+ try:
70
+ ticker = yf.Ticker(symbol)
71
+
72
+ # Calculate start/end dynamically
73
+ end_date = datetime.today()
74
+ if period.endswith("d"):
75
+ days = int(period[:-1])
76
+ elif period.endswith("mo"):
77
+ days = int(period[:-2]) * 30
78
+ elif period.endswith("y"):
79
+ days = int(period[:-1]) * 365
80
+ else:
81
+ days = 30
82
+ start_date = end_date - timedelta(days=days)
83
+
84
+ data = ticker.history(
85
+ start=start_date.strftime("%Y-%m-%d"),
86
+ end=end_date.strftime("%Y-%m-%d")
87
+ )
88
+
89
+ if data.empty:
90
+ return f"No data for {symbol}."
91
+
92
+ recent_change = data["Close"].iloc[-1] - data["Close"].iloc[0]
93
+ pct_change = (recent_change / data["Close"].iloc[0]) * 100
94
+
95
+ sentiment = "Neutral"
96
+ if pct_change > 2:
97
+ sentiment = "Bullish"
98
+ elif pct_change < -2:
99
+ sentiment = "Bearish"
100
+
101
+ return f"{symbol} market sentiment ({period}): {sentiment} ({pct_change:.2f}% change)"
102
+
103
+ except Exception as e:
104
+ return f"Error fetching market sentiment for '{symbol}': {e}"
105
+
106
+ def _get_history(symbol: str, period: str = "1mo") -> str:
107
+ print(f"[DEBUG] get_history called for symbol='{symbol}', period='{period}'")
108
+ try:
109
+ ticker = yf.Ticker(symbol)
110
+
111
+ # Calculate start/end dynamically
112
+ end_date = datetime.today()
113
+ if period.endswith("d"):
114
+ days = int(period[:-1])
115
+ elif period.endswith("mo"):
116
+ days = int(period[:-2]) * 30
117
+ elif period.endswith("y"):
118
+ days = int(period[:-1]) * 365
119
+ else:
120
+ days = 30
121
+ start_date = end_date - timedelta(days=days)
122
+
123
+ data = ticker.history(
124
+ start=start_date.strftime("%Y-%m-%d"),
125
+ end=end_date.strftime("%Y-%m-%d")
126
+ )
127
+
128
+ if data.empty:
129
+ return f"No historical data found for '{symbol}'."
130
+
131
+ # Convert to JSON format (reset index to include Date)
132
+ # return data.tail(5).reset_index().to_json(orient='records', date_format='iso')
133
+ return f"Historical data for {symbol} ({period}):\n{data.tail(5).to_string()}"
134
+
135
+ except Exception as e:
136
+ return f"Error fetching historical data for '{symbol}': {e}"
137
+
138
+ def _get_analyst_recommendations(symbol: str) -> str:
139
+ print(f"[DEBUG] get_analyst_recommendations called for symbol='{symbol}'")
140
+ try:
141
+ ticker = yf.Ticker(symbol)
142
+ recs = ticker.recommendations
143
+ if recs is None or recs.empty:
144
+ return f"No analyst recommendations found for {symbol}."
145
+
146
+ # Format the last few recommendations
147
+ latest = recs.tail(5)
148
+ return f"Analyst Recommendations for {symbol}:\n{latest.to_string()}"
149
+ except Exception as e:
150
+ return f"Error fetching recommendations for '{symbol}': {e}"
151
+
152
+ def _get_earnings_calendar(symbol: str) -> str:
153
+ print(f"[DEBUG] get_earnings_calendar called for symbol='{symbol}'")
154
+ try:
155
+ ticker = yf.Ticker(symbol)
156
+ calendar = ticker.calendar
157
+ if calendar is None:
158
+ return f"No earnings calendar found for {symbol}."
159
+
160
+ # Handle dict (new yfinance) or DataFrame (old yfinance)
161
+ if isinstance(calendar, dict):
162
+ if not calendar:
163
+ return f"No earnings calendar found for {symbol}."
164
+ elif hasattr(calendar, 'empty') and calendar.empty:
165
+ return f"No earnings calendar found for {symbol}."
166
+
167
+ return f"Earnings Calendar for {symbol}:\n{calendar}"
168
+ except Exception as e:
169
+ return f"Error fetching earnings calendar for '{symbol}': {e}"
170
+
171
+ @function_tool
172
+ def get_summary(symbol: str, period: str = "1d", interval: str = "1h") -> str:
173
+ """
174
+ Fetch the latest summary information and intraday price data for a given ticker.
175
+ Ensures recent data is retrieved by calculating start/end dates dynamically.
176
+
177
+ Parameters:
178
+ -----------
179
+ symbol : str
180
+ The ticker symbol (e.g., "AAPL", "GOOG", "BTC-USD").
181
+ period : str, optional (default="1d")
182
+ Time range for price data. Examples: "1d", "5d", "1mo", "3mo".
183
+ interval : str, optional (default="1h")
184
+ Granularity of the data. Examples: "1m", "5m", "1h", "1d".
185
+
186
+ Returns:
187
+ --------
188
+ str
189
+ A formatted string containing:
190
+ - Company/ticker name
191
+ - Current price and change
192
+ - Open, High, Low prices
193
+ - Volume
194
+ - Period and interval used
195
+ """
196
+ return _get_summary(symbol, period, interval)
197
+
198
+ @function_tool
199
+ def get_market_sentiment(symbol: str, period: str = "1mo") -> str:
200
+ """
201
+ Analyze recent price changes and provide a simple market sentiment.
202
+ Uses dynamic start/end dates to ensure recent data.
203
+
204
+ This tool computes the percentage change over the specified period and
205
+ classifies the sentiment as:
206
+ - Bullish (if price increased >2%)
207
+ - Bearish (if price decreased >2%)
208
+ - Neutral (otherwise)
209
+
210
+ Parameters:
211
+ -----------
212
+ symbol : str
213
+ The ticker symbol (e.g., "AAPL", "GOOG", "BTC-USD").
214
+ period : str, optional (default="1mo")
215
+ Time range to analyze. Examples: "7d", "1mo", "3mo".
216
+
217
+ Returns:
218
+ --------
219
+ str
220
+ A human-readable sentiment string including percentage change.
221
+ """
222
+ return _get_market_sentiment(symbol, period)
223
+
224
+ @function_tool
225
+ def get_history(symbol: str, period: str = "1mo") -> str:
226
+ """
227
+ Fetch historical price data for a given ticker.
228
+ Ensures recent data is retrieved dynamically using start/end dates.
229
+
230
+ Parameters:
231
+ -----------
232
+ symbol : str
233
+ The ticker symbol (e.g., "AAPL", "GOOG", "BTC-USD").
234
+ period : str, optional (default="1mo")
235
+ The length of historical data to retrieve. Examples: "1d", "5d", "1mo", "3mo", "1y", "5y".
236
+
237
+ Returns:
238
+ --------
239
+ str
240
+ A formatted string showing the last 5 rows of historical prices (Open, High, Low, Close, Volume).
241
+ """
242
+ return _get_history(symbol, period)
243
+
244
+ @function_tool
245
+ def get_analyst_recommendations(symbol: str) -> str:
246
+ """
247
+ Fetch analyst recommendations for a given ticker.
248
+
249
+ Parameters:
250
+ -----------
251
+ symbol : str
252
+ The ticker symbol.
253
+
254
+ Returns:
255
+ --------
256
+ str
257
+ Formatted string string of analyst recommendations.
258
+ """
259
+ return _get_analyst_recommendations(symbol)
260
+
261
+ @function_tool
262
+ def get_earnings_calendar(symbol: str) -> str:
263
+ """
264
+ Fetch the next earnings date for a ticker.
265
+
266
+ Parameters:
267
+ -----------
268
+ symbol : str
269
+ The ticker symbol.
270
+
271
+ Returns:
272
+ --------
273
+ str
274
+ Next earnings date info.
275
+ """
276
+ return _get_earnings_calendar(symbol)
277
+
src/stock-investment-analyst/utility/__init__.py ADDED
File without changes
src/stock-investment-analyst/utility/autogen_model_factory.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from azure.identity import DefaultAzureCredential, get_bearer_token_provider
3
+
4
+ class AutoGenModelFactory:
5
+ """
6
+ Factory for creating AutoGen compatible model instances.
7
+ """
8
+
9
+ @staticmethod
10
+ def get_model(provider: str = "azure", # azure, openai, google, groq, ollama
11
+ model_name: str = "gpt-4o",
12
+ temperature: float = 0,
13
+ model_info: dict = None
14
+ ):
15
+ """
16
+ Returns an AutoGen OpenAIChatCompletionClient instance.
17
+ """
18
+
19
+ # Lazy import to avoid dependency issues if autogen is not installed
20
+ try:
21
+ from autogen_ext.models.openai import OpenAIChatCompletionClient
22
+ except ImportError as e:
23
+ raise ImportError("AutoGen libraries (autogen-agentchat, autogen-ext[openai]) are not installed.") from e
24
+
25
+ # ----------------------------------------------------------------------
26
+ # AZURE
27
+ # ----------------------------------------------------------------------
28
+ if provider.lower() == "azure":
29
+ token_provider = get_bearer_token_provider(
30
+ DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"
31
+ )
32
+ return OpenAIChatCompletionClient(
33
+ model=model_name,
34
+ azure_endpoint=os.environ["AZURE_OPENAI_API_URI"],
35
+ api_version=os.environ["AZURE_OPENAI_API_VERSION"],
36
+ azure_ad_token_provider=token_provider,
37
+ temperature=temperature,
38
+ )
39
+
40
+ # ----------------------------------------------------------------------
41
+ # OPENAI
42
+ # ----------------------------------------------------------------------
43
+ elif provider.lower() == "openai":
44
+ return OpenAIChatCompletionClient(
45
+ model=model_name,
46
+ api_key=os.environ["OPENAI_API_KEY"],
47
+ temperature=temperature,
48
+ )
49
+
50
+ # ----------------------------------------------------------------------
51
+ # GOOGLE (GEMINI) via OpenAI Compat
52
+ # ----------------------------------------------------------------------
53
+ elif provider.lower() == "google" or provider.lower() == "gemini":
54
+ if model_info is None:
55
+ model_info = {
56
+ "family": "gemini",
57
+ "vision": False,
58
+ "function_calling": True,
59
+ "json_output": True,
60
+ "structured_output": False
61
+ }
62
+
63
+ return OpenAIChatCompletionClient(
64
+ model=model_name,
65
+ base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
66
+ api_key=os.environ["GOOGLE_API_KEY"],
67
+ model_info=model_info,
68
+ temperature=temperature,
69
+ max_tokens=2048,
70
+ structured_output=False, # Disable for Gemini compatibility
71
+ extra_headers={"x-goog-api-key": os.environ["GOOGLE_API_KEY"]}
72
+ )
73
+
74
+ # ----------------------------------------------------------------------
75
+ # GROQ
76
+ # ----------------------------------------------------------------------
77
+ elif provider.lower() == "groq":
78
+ if model_info is None:
79
+ model_info = {
80
+ "family": "llama", # Use llama family for Groq
81
+ "vision": False,
82
+ "function_calling": True,
83
+ "json_output": True,
84
+ "structured_output": False
85
+ }
86
+
87
+ return OpenAIChatCompletionClient(
88
+ model=model_name,
89
+ base_url="https://api.groq.com/openai/v1",
90
+ api_key=os.environ["GROQ_API_KEY"],
91
+ model_info=model_info,
92
+ temperature=temperature,
93
+ structured_output=False, # Disable for Groq compatibility
94
+ max_tokens=2048
95
+ )
96
+
97
+ # ----------------------------------------------------------------------
98
+ # OLLAMA
99
+ # ----------------------------------------------------------------------
100
+ elif provider.lower() == "ollama":
101
+ # Ensure model_info defaults to empty dict if None
102
+ info = model_info if model_info is not None else {}
103
+ return OpenAIChatCompletionClient(
104
+ model=model_name,
105
+ base_url="http://localhost:11434/v1",
106
+ api_key="ollama", # dummy key
107
+ model_info=info,
108
+ temperature=temperature,
109
+ )
110
+
111
+ else:
112
+ raise ValueError(f"Unsupported AutoGen provider: {provider}")
uv.lock CHANGED
The diff for this file is too large to render. See raw diff