Skip to content

Commit 516c672

Browse files
authored
Merge pull request #224 from faridun-ag2/add-ag2-multiagent-financial-analysis
Add multi-agent financial news analysis notebook
2 parents 3eeabc7 + 726aae8 commit 516c672

1 file changed

Lines changed: 234 additions & 0 deletions

File tree

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Multi-Agent Financial News Analysis Pipeline\n",
8+
"\n",
9+
"Financial analysts spend hours reading news, assessing sentiment, and synthesizing findings into actionable insights. In this notebook, we build an automated pipeline where multiple AI agents collaborate to do this work — fetching financial news, running sentiment analysis with FinGPT models, and producing a structured investment brief.\n",
10+
"\n",
11+
"We use [AG2](https://ag2.ai) to orchestrate three specialized agents:\n",
12+
"- A **News Researcher** that gathers and summarizes recent financial news for a given ticker\n",
13+
"- A **Sentiment Analyst** that runs FinGPT sentiment classification on the headlines\n",
14+
"- An **Investment Advisor** that synthesizes findings into an actionable brief\n",
15+
"\n",
16+
"Each agent has access to real tools and works with live data from the Hugging Face Hub and financial APIs."
17+
]
18+
},
19+
{
20+
"cell_type": "markdown",
21+
"metadata": {},
22+
"source": [
23+
"## Setup"
24+
]
25+
},
26+
{
27+
"cell_type": "code",
28+
"execution_count": null,
29+
"metadata": {},
30+
"outputs": [],
31+
"source": [
32+
"!pip install \"ag2[openai]>=0.11.4,<1.0\" huggingface_hub transformers torch yfinance -q"
33+
]
34+
},
35+
{
36+
"cell_type": "markdown",
37+
"metadata": {},
38+
"source": [
39+
"## Connecting to a Language Model\n",
40+
"\n",
41+
"We power our agents with an open-source model via the Hugging Face Inference API, which provides an OpenAI-compatible endpoint. You'll need a [HF token](https://huggingface.co/settings/tokens)."
42+
]
43+
},
44+
{
45+
"cell_type": "code",
46+
"execution_count": null,
47+
"metadata": {},
48+
"outputs": [],
49+
"source": "import os\nfrom huggingface_hub import get_token\nfrom autogen import AssistantAgent, UserProxyAgent, GroupChat, GroupChatManager, LLMConfig\n\nhf_token = get_token()\n\nllm_config = LLMConfig({\n \"model\": \"Qwen/Qwen2.5-Coder-32B-Instruct\",\n \"api_key\": hf_token,\n \"api_type\": \"openai\",\n \"base_url\": \"https://router.huggingface.co/v1\",\n})"
50+
},
51+
{
52+
"cell_type": "markdown",
53+
"metadata": {},
54+
"source": [
55+
"## Building the Financial Analysis Tools\n",
56+
"\n",
57+
"Before creating agents, we give them tools to work with real financial data:\n",
58+
"\n",
59+
"1. **get_stock_info** — fetch recent price data and key metrics for a ticker using `yfinance`\n",
60+
"2. **get_financial_news** — retrieve recent news headlines for a company via `yfinance`\n",
61+
"3. **analyze_sentiment** — run FinGPT-style sentiment classification on financial text using a HuggingFace model"
62+
]
63+
},
64+
{
65+
"cell_type": "code",
66+
"execution_count": null,
67+
"metadata": {},
68+
"outputs": [],
69+
"source": "import json\nfrom typing import Annotated\nfrom datetime import datetime, timedelta\n\nimport yfinance as yf\n\n\ndef get_stock_info(\n ticker: Annotated[str, \"Stock ticker symbol, e.g. 'AAPL', 'MSFT', 'GOOGL'\"],\n) -> str:\n \"\"\"Fetch recent stock price data and key financial metrics.\"\"\"\n try:\n stock = yf.Ticker(ticker)\n info = stock.info\n\n # Get recent price history\n hist = stock.history(period=\"1mo\")\n if hist.empty:\n return json.dumps({\"error\": f\"No price data found for {ticker}\"})\n\n current_price = hist[\"Close\"].iloc[-1]\n month_ago_price = hist[\"Close\"].iloc[0]\n price_change_pct = ((current_price - month_ago_price) / month_ago_price) * 100\n\n result = {\n \"ticker\": ticker,\n \"company_name\": info.get(\"longName\", ticker),\n \"sector\": info.get(\"sector\", \"unknown\"),\n \"current_price\": round(current_price, 2),\n \"price_change_1mo_pct\": round(price_change_pct, 2),\n \"market_cap\": info.get(\"marketCap\", \"N/A\"),\n \"pe_ratio\": info.get(\"trailingPE\", \"N/A\"),\n \"52w_high\": info.get(\"fiftyTwoWeekHigh\", \"N/A\"),\n \"52w_low\": info.get(\"fiftyTwoWeekLow\", \"N/A\"),\n }\n return json.dumps(result, indent=2)\n except Exception as e:\n return json.dumps({\"error\": str(e)})\n\n\ndef get_financial_news(\n ticker: Annotated[str, \"Stock ticker symbol\"],\n max_headlines: Annotated[int, \"Maximum number of headlines to return\"] = 10,\n) -> str:\n \"\"\"Get recent financial news headlines for a stock ticker.\"\"\"\n try:\n stock = yf.Ticker(ticker)\n news = stock.news\n\n if not news:\n return json.dumps({\"ticker\": ticker, \"headlines\": [], \"message\": \"No recent news found\"})\n\n headlines = []\n for item in news[:max_headlines]:\n content = item.get(\"content\", item)\n provider = content.get(\"provider\", {})\n pub_date = content.get(\"pubDate\", \"\")\n headlines.append({\n \"title\": content.get(\"title\", \"\"),\n \"publisher\": provider.get(\"displayName\", \"\"),\n \"published\": pub_date[:10] if pub_date else \"unknown\",\n })\n\n return json.dumps({\"ticker\": ticker, \"headlines\": headlines}, indent=2)\n except Exception as e:\n return json.dumps({\"error\": str(e)})\n\n\ndef analyze_sentiment(\n texts: Annotated[list[str], \"List of financial text snippets to analyze (headlines, summaries, etc.)\"],\n) -> str:\n \"\"\"Run sentiment analysis on financial texts using a FinGPT-aligned model.\n\n Uses a financial sentiment model from HuggingFace to classify each text\n as positive, negative, or neutral with confidence scores.\n \"\"\"\n from transformers import pipeline\n\n # Use a financial sentiment model — ProsusAI/finbert is well-established\n # and aligns with FinGPT's sentiment analysis focus\n try:\n classifier = pipeline(\n \"sentiment-analysis\",\n model=\"ProsusAI/finbert\",\n return_all_scores=False,\n )\n\n results = []\n for text in texts:\n pred = classifier(text[:512])[0] # Truncate to model max length\n results.append({\n \"text\": text[:100] + (\"...\" if len(text) > 100 else \"\"),\n \"sentiment\": pred[\"label\"],\n \"confidence\": round(pred[\"score\"], 4),\n })\n\n # Aggregate summary\n sentiments = [r[\"sentiment\"] for r in results]\n summary = {\n \"positive\": sentiments.count(\"positive\"),\n \"negative\": sentiments.count(\"negative\"),\n \"neutral\": sentiments.count(\"neutral\"),\n \"total\": len(results),\n }\n\n return json.dumps({\"results\": results, \"summary\": summary}, indent=2)\n except Exception as e:\n return json.dumps({\"error\": str(e)})"
70+
},
71+
{
72+
"cell_type": "markdown",
73+
"metadata": {},
74+
"source": [
75+
"## Creating the Agent Team\n",
76+
"\n",
77+
"Each agent has a focused role and access to specific tools:\n",
78+
"\n",
79+
"- **News Researcher** — fetches stock data and news, builds a factual overview\n",
80+
"- **Sentiment Analyst** — runs FinGPT-style sentiment classification on the headlines\n",
81+
"- **Investment Advisor** — synthesizes everything into a structured brief with a recommendation\n",
82+
"\n",
83+
"We use AG2's decorator pattern to register which agent can call which tool."
84+
]
85+
},
86+
{
87+
"cell_type": "code",
88+
"execution_count": null,
89+
"metadata": {},
90+
"outputs": [],
91+
"source": "researcher = AssistantAgent(\n name=\"News_Researcher\",\n system_message=(\n \"You are a financial news researcher. Your job is to gather data about \"\n \"the requested stock ticker. Use get_stock_info to fetch price and metrics, \"\n \"then use get_financial_news to get recent headlines. \"\n \"Present your findings clearly: company overview, recent price action, \"\n \"and a numbered list of the most relevant headlines. \"\n \"Only use the tools provided — do not make up data.\"\n ),\n llm_config=llm_config,\n)\n\nanalyst = AssistantAgent(\n name=\"Sentiment_Analyst\",\n system_message=(\n \"You are a financial sentiment analyst specializing in NLP-based analysis. \"\n \"When the News Researcher has gathered headlines, use analyze_sentiment \"\n \"to run sentiment classification on those headlines. \"\n \"Present results in a structured table showing each headline, its sentiment \"\n \"(positive/negative/neutral), and confidence score. \"\n \"Provide an overall sentiment summary. \"\n \"Only use the tools provided — do not invent scores.\"\n ),\n llm_config=llm_config,\n)\n\nadvisor = AssistantAgent(\n name=\"Investment_Advisor\",\n system_message=(\n \"You are an investment advisor. Based on the News Researcher's data and \"\n \"the Sentiment Analyst's classification results, produce a structured \"\n \"investment brief. Include:\\n\"\n \"1. Company snapshot (price, metrics, sector)\\n\"\n \"2. News sentiment summary (% positive/negative/neutral)\\n\"\n \"3. Key risks and catalysts identified from the news\\n\"\n \"4. Overall outlook (bullish/bearish/neutral) with reasoning\\n\\n\"\n \"IMPORTANT: Add a disclaimer that this is AI-generated analysis, not \"\n \"financial advice. End your brief with TERMINATE.\"\n ),\n llm_config=llm_config,\n)\n\nexecutor = UserProxyAgent(\n name=\"Executor\",\n human_input_mode=\"NEVER\",\n max_consecutive_auto_reply=10,\n code_execution_config=False,\n)\n\n# Register tools — Researcher and Analyst can call them, Executor runs them\nfor tool_fn, description in [\n (get_stock_info, \"Fetch recent stock price data and key financial metrics for a ticker\"),\n (get_financial_news, \"Get recent financial news headlines for a stock ticker\"),\n (analyze_sentiment, \"Run sentiment analysis on a list of financial text snippets\"),\n]:\n executor.register_for_execution()(tool_fn)\n researcher.register_for_llm(description=description)(tool_fn)\n analyst.register_for_llm(description=description)(tool_fn)"
92+
},
93+
{
94+
"cell_type": "markdown",
95+
"metadata": {},
96+
"source": [
97+
"## Running the Pipeline\n",
98+
"\n",
99+
"Let's analyze a stock. The GroupChat manager coordinates the agents — the Researcher gathers data first, the Analyst runs sentiment classification, and the Advisor wraps up with a structured brief."
100+
]
101+
},
102+
{
103+
"cell_type": "code",
104+
"execution_count": null,
105+
"metadata": {},
106+
"outputs": [],
107+
"source": [
108+
"group_chat = GroupChat(\n",
109+
" agents=[executor, researcher, analyst, advisor],\n",
110+
" messages=[],\n",
111+
" max_round=10,\n",
112+
" speaker_selection_method=\"auto\",\n",
113+
")\n",
114+
"\n",
115+
"manager = GroupChatManager(\n",
116+
" groupchat=group_chat,\n",
117+
" llm_config=llm_config,\n",
118+
")\n",
119+
"\n",
120+
"executor.run(\n",
121+
" manager,\n",
122+
" message=(\n",
123+
" \"Analyze NVDA (NVIDIA). Fetch the latest stock data and news headlines, \"\n",
124+
" \"run sentiment analysis on the headlines, and produce an investment brief \"\n",
125+
" \"with an overall outlook.\"\n",
126+
" ),\n",
127+
").process()"
128+
]
129+
},
130+
{
131+
"cell_type": "markdown",
132+
"metadata": {},
133+
"source": [
134+
"## Reviewing the Agent Conversation\n",
135+
"\n",
136+
"Let's trace how the agents collaborated — who spoke, what tools they called, and how the analysis was built step by step:"
137+
]
138+
},
139+
{
140+
"cell_type": "code",
141+
"execution_count": null,
142+
"metadata": {},
143+
"outputs": [],
144+
"source": [
145+
"for msg in group_chat.messages:\n",
146+
" name = msg.get(\"name\", msg.get(\"role\", \"unknown\"))\n",
147+
" content = msg.get(\"content\", \"\")\n",
148+
" if content and content.strip():\n",
149+
" print(f\"{'='*60}\")\n",
150+
" print(f\">> {name}:\")\n",
151+
" print(f\"{content[:800]}\")\n",
152+
" print()"
153+
]
154+
},
155+
{
156+
"cell_type": "markdown",
157+
"metadata": {},
158+
"source": [
159+
"## Try It Yourself\n",
160+
"\n",
161+
"Change the ticker and question below to analyze any stock:"
162+
]
163+
},
164+
{
165+
"cell_type": "code",
166+
"execution_count": null,
167+
"metadata": {},
168+
"outputs": [],
169+
"source": [
170+
"# Change this to any ticker you want to analyze!\n",
171+
"your_ticker = \"TSLA\"\n",
172+
"your_question = (\n",
173+
" f\"Analyze {your_ticker}. Fetch stock data and recent news, \"\n",
174+
" f\"run sentiment analysis on the headlines, and produce an \"\n",
175+
" f\"investment brief with an overall outlook and key risks.\"\n",
176+
")\n",
177+
"\n",
178+
"group_chat_2 = GroupChat(\n",
179+
" agents=[executor, researcher, analyst, advisor],\n",
180+
" messages=[],\n",
181+
" max_round=10,\n",
182+
" speaker_selection_method=\"auto\",\n",
183+
")\n",
184+
"\n",
185+
"manager_2 = GroupChatManager(groupchat=group_chat_2, llm_config=llm_config)\n",
186+
"executor.run(manager_2, message=your_question).process()\n",
187+
"\n",
188+
"# Print the advisor's brief\n",
189+
"for msg in reversed(group_chat_2.messages):\n",
190+
" if msg.get(\"name\") == \"Investment_Advisor\" and msg.get(\"content\", \"\").strip():\n",
191+
" print(\"Investment Brief:\")\n",
192+
" print(msg[\"content\"].replace(\"TERMINATE\", \"\").strip())\n",
193+
" break"
194+
]
195+
},
196+
{
197+
"cell_type": "markdown",
198+
"metadata": {},
199+
"source": [
200+
"## What We Built\n",
201+
"\n",
202+
"This notebook demonstrated a practical financial analysis pipeline where:\n",
203+
"\n",
204+
"1. **The News Researcher** fetched real-time stock data and news via `yfinance`\n",
205+
"2. **The Sentiment Analyst** ran FinGPT-style sentiment classification on headlines using FinBERT from HuggingFace\n",
206+
"3. **The Investment Advisor** synthesized data + sentiment into a structured investment brief\n",
207+
"\n",
208+
"Each agent has a focused role with access to real tools — they call live APIs, run actual ML inference, and work with real market data.\n",
209+
"\n",
210+
"**Extending this pattern:**\n",
211+
"- Swap FinBERT for a FinGPT fine-tuned model (e.g., `FinGPT/fingpt-sentiment_llama2-13b_lora`) for potentially better financial sentiment accuracy\n",
212+
"- Add a **Risk Manager** agent that cross-references sentiment with volatility metrics\n",
213+
"- Add an **SEC Filings** tool to pull 10-K/10-Q data for fundamental analysis\n",
214+
"- Connect to the [FinGPT Forecaster](https://huggingface.co/FinGPT/fingpt-forecaster_dow30_llama2-7b_lora) for price movement predictions\n",
215+
"- Run the pipeline on a portfolio of tickers for batch analysis\n",
216+
"\n",
217+
"For more on multi-agent orchestration patterns, see the [AG2 documentation](https://docs.ag2.ai)."
218+
]
219+
}
220+
],
221+
"metadata": {
222+
"kernelspec": {
223+
"display_name": "Python 3",
224+
"language": "python",
225+
"name": "python3"
226+
},
227+
"language_info": {
228+
"name": "python",
229+
"version": "3.11.0"
230+
}
231+
},
232+
"nbformat": 4,
233+
"nbformat_minor": 4
234+
}

0 commit comments

Comments
 (0)