Navada25 Claude commited on
Commit
ce180e5
·
1 Parent(s): 3967774

Deploy CFA AI Agent with Finance-Llama-8B

Browse files

- Added complete financial analysis agent with LangChain integration
- Implemented comprehensive financial tools (DCF, ratios, risk metrics)
- Added real-time market data fetching capabilities
- Optimized model loading for memory efficiency with 8-bit quantization
- Updated requirements.txt for HF Spaces compatibility
- Enhanced README with detailed feature descriptions

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

README.md CHANGED
@@ -1,12 +1,53 @@
1
  ---
2
- title: CFA Ai Agent
3
- emoji: 👁
4
- colorFrom: pink
5
- colorTo: yellow
6
- sdk: gradio
7
- sdk_version: 5.49.1
8
  app_file: app.py
9
  pinned: false
 
 
 
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: CFA AI Agent
3
+ emoji: 📊
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: chainlit
7
+ sdk_version: 1.0.0
8
  app_file: app.py
9
  pinned: false
10
+ python_version: 3.11
11
+ models:
12
+ - tarun7r/Finance-Llama-8B
13
+ license: mit
14
  ---
15
 
16
+ # 📊 CFA AI Agent
17
+
18
+ A specialized financial analysis AI agent powered by Finance-Llama-8B and equipped with comprehensive financial tools.
19
+
20
+ ## 🚀 Features
21
+
22
+ - **Advanced Financial Analysis**: DCF valuation, ratio analysis, risk assessment
23
+ - **Real-time Market Data**: Stock prices, historical data, financial statements
24
+ - **Portfolio Management**: Beta calculation, WACC, Sharpe ratio analysis
25
+ - **Interactive Chat Interface**: Powered by Chainlit for seamless user experience
26
+
27
+ ## 💼 Capabilities
28
+
29
+ - Discounted Cash Flow (DCF) valuation
30
+ - Financial ratio analysis and comparison
31
+ - Risk metrics calculation (Beta, Sharpe ratio)
32
+ - Weighted Average Cost of Capital (WACC)
33
+ - Stock price analysis and comparison
34
+ - Market data fetching and analysis
35
+
36
+ ## 🔧 Built With
37
+
38
+ - **Model**: Finance-Llama-8B (specialized for financial analysis)
39
+ - **Framework**: LangChain for agent orchestration
40
+ - **Interface**: Chainlit for web interface
41
+ - **Data**: Yahoo Finance for real-time market data
42
+
43
+ ## 📈 Use Cases
44
+
45
+ Perfect for financial professionals, students, and anyone needing:
46
+ - Investment analysis and valuation
47
+ - Portfolio risk assessment
48
+ - Financial statement analysis
49
+ - Market research and comparison
50
+
51
+ ---
52
+
53
+ *This is a CFA-level financial analysis tool designed to assist with professional financial analysis tasks.*
agent.py ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CFA AI Agent - LangChain Agent Setup
3
+ This module sets up the LangChain agent with Finance-Llama-8B model and financial tools.
4
+ """
5
+
6
+ import os
7
+ import torch
8
+ from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
9
+ from langchain_community.llms import HuggingFacePipeline
10
+ from langchain.agents import initialize_agent, AgentType
11
+ from langchain.memory import ConversationBufferMemory
12
+ from langchain.schema import SystemMessage
13
+ from langchain.prompts import MessagesPlaceholder
14
+ from typing import List, Any, Optional
15
+
16
+ # Import our custom tools
17
+ from tools.finance_tools import (
18
+ calculate_dcf,
19
+ calculate_sharpe_ratio,
20
+ compare_pe_ratios,
21
+ calculate_beta,
22
+ calculate_wacc,
23
+ financial_ratios_analysis
24
+ )
25
+ from tools.data_fetcher import (
26
+ get_stock_price,
27
+ get_historical_data,
28
+ get_company_info,
29
+ get_financial_statements,
30
+ get_market_indices,
31
+ compare_stocks
32
+ )
33
+
34
+
35
+ class CFAAgent:
36
+ """
37
+ CFA AI Agent that combines Finance-Llama-8B model with financial analysis tools.
38
+ """
39
+
40
+ def __init__(self, model_name: str = "tarun7r/Finance-Llama-8B"):
41
+ """
42
+ Initialize the CFA Agent with model and tools.
43
+
44
+ Args:
45
+ model_name: Hugging Face model name for financial analysis
46
+ """
47
+ self.model_name = model_name
48
+ self.tokenizer = None
49
+ self.model = None
50
+ self.llm = None
51
+ self.agent = None
52
+ self.memory = None
53
+ self._setup_model()
54
+ self._setup_tools()
55
+ self._setup_agent()
56
+
57
+ def _setup_model(self):
58
+ """Load and setup the Finance-Llama-8B model."""
59
+ try:
60
+ print(f"Loading model: {self.model_name}")
61
+
62
+ # Check if CUDA is available
63
+ device = "cuda" if torch.cuda.is_available() else "cpu"
64
+ print(f"Using device: {device}")
65
+
66
+ # Load tokenizer
67
+ self.tokenizer = AutoTokenizer.from_pretrained(
68
+ self.model_name,
69
+ trust_remote_code=True
70
+ )
71
+
72
+ # Add pad token if not present
73
+ if self.tokenizer.pad_token is None:
74
+ self.tokenizer.pad_token = self.tokenizer.eos_token
75
+
76
+ # Load model with appropriate settings and memory optimization
77
+ if device == "cuda":
78
+ self.model = AutoModelForCausalLM.from_pretrained(
79
+ self.model_name,
80
+ torch_dtype=torch.float16,
81
+ device_map="auto",
82
+ trust_remote_code=True,
83
+ low_cpu_mem_usage=True,
84
+ load_in_8bit=True, # Enable 8-bit quantization for memory efficiency
85
+ max_memory={0: "6GB"} # Limit GPU memory usage
86
+ )
87
+ else:
88
+ # For CPU, use aggressive memory optimization
89
+ self.model = AutoModelForCausalLM.from_pretrained(
90
+ self.model_name,
91
+ trust_remote_code=True,
92
+ low_cpu_mem_usage=True,
93
+ torch_dtype=torch.float32,
94
+ device_map="cpu",
95
+ max_memory={"cpu": "8GB"} # Limit CPU memory usage
96
+ )
97
+
98
+ # Create pipeline
99
+ pipe = pipeline(
100
+ "text-generation",
101
+ model=self.model,
102
+ tokenizer=self.tokenizer,
103
+ max_new_tokens=512,
104
+ temperature=0.1,
105
+ do_sample=True,
106
+ pad_token_id=self.tokenizer.eos_token_id,
107
+ repetition_penalty=1.1
108
+ )
109
+
110
+ # Wrap in LangChain
111
+ self.llm = HuggingFacePipeline(pipeline=pipe)
112
+ print("✅ Model loaded successfully")
113
+
114
+ except Exception as e:
115
+ print(f"❌ Error loading model: {str(e)}")
116
+ # Fallback to a smaller model or OpenAI if Finance-Llama-8B fails
117
+ self._setup_fallback_model()
118
+
119
+ def _setup_fallback_model(self):
120
+ """Setup a fallback model if Finance-Llama-8B fails to load."""
121
+ try:
122
+ print("Setting up fallback model...")
123
+ from langchain_community.llms import OpenAI
124
+
125
+ # Check for OpenAI API key
126
+ if os.getenv("OPENAI_API_KEY"):
127
+ self.llm = OpenAI(
128
+ temperature=0.1,
129
+ model_name="gpt-3.5-turbo-instruct",
130
+ max_tokens=512
131
+ )
132
+ print("✅ Using OpenAI GPT-3.5 as fallback")
133
+ else:
134
+ raise ValueError("No OpenAI API key found")
135
+
136
+ except Exception as e:
137
+ print(f"❌ Fallback model failed: {str(e)}")
138
+ # Last resort: use a very small local model
139
+ try:
140
+ pipe = pipeline(
141
+ "text-generation",
142
+ model="distilgpt2",
143
+ max_new_tokens=256,
144
+ temperature=0.7
145
+ )
146
+ self.llm = HuggingFacePipeline(pipeline=pipe)
147
+ print("✅ Using DistilGPT2 as emergency fallback")
148
+ except Exception as final_e:
149
+ raise RuntimeError(f"All model loading attempts failed: {final_e}")
150
+
151
+ def _setup_tools(self):
152
+ """Setup all available financial analysis tools."""
153
+ self.tools = [
154
+ # Finance calculation tools
155
+ calculate_dcf,
156
+ calculate_sharpe_ratio,
157
+ compare_pe_ratios,
158
+ calculate_beta,
159
+ calculate_wacc,
160
+ financial_ratios_analysis,
161
+
162
+ # Data fetching tools
163
+ get_stock_price,
164
+ get_historical_data,
165
+ get_company_info,
166
+ get_financial_statements,
167
+ get_market_indices,
168
+ compare_stocks
169
+ ]
170
+ print(f"✅ Loaded {len(self.tools)} financial analysis tools")
171
+
172
+ def _setup_agent(self):
173
+ """Setup the LangChain agent with memory and tools."""
174
+ try:
175
+ # Setup conversation memory
176
+ self.memory = ConversationBufferMemory(
177
+ memory_key="chat_history",
178
+ return_messages=True,
179
+ output_key="output"
180
+ )
181
+
182
+ # Initialize agent
183
+ self.agent = initialize_agent(
184
+ tools=self.tools,
185
+ llm=self.llm,
186
+ agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
187
+ memory=self.memory,
188
+ verbose=True,
189
+ handle_parsing_errors=True,
190
+ max_iterations=3,
191
+ early_stopping_method="generate"
192
+ )
193
+
194
+ # Add system message for financial context
195
+ system_message = """You are a CFA (Chartered Financial Analyst) AI assistant specialized in financial analysis, investment valuation, and portfolio management.
196
+
197
+ Your expertise includes:
198
+ - Financial statement analysis and ratio calculations
199
+ - Valuation models (DCF, comparable company analysis, etc.)
200
+ - Risk assessment and portfolio theory
201
+ - Market analysis and economic indicators
202
+ - Investment recommendations based on fundamental analysis
203
+
204
+ When answering questions:
205
+ 1. Use the available financial tools to fetch real data when needed
206
+ 2. Provide clear, professional explanations suitable for CFA-level analysis
207
+ 3. Show your calculations and reasoning
208
+ 4. Consider both quantitative and qualitative factors
209
+ 5. Acknowledge limitations and assumptions in your analysis
210
+
211
+ You have access to real-time financial data and calculation tools. Use them to provide accurate, data-driven insights."""
212
+
213
+ # Store system message for context
214
+ self.system_message = system_message
215
+ print("✅ Agent initialized successfully")
216
+
217
+ except Exception as e:
218
+ print(f"❌ Error setting up agent: {str(e)}")
219
+ raise
220
+
221
+ def query(self, question: str) -> str:
222
+ """
223
+ Process a financial query using the CFA agent.
224
+
225
+ Args:
226
+ question: User's financial question or request
227
+
228
+ Returns:
229
+ Agent's response with analysis and recommendations
230
+ """
231
+ try:
232
+ # Enhance the question with context
233
+ enhanced_question = f"""As a CFA analyst, please help with the following:
234
+
235
+ {question}
236
+
237
+ Please provide a thorough analysis using available data and financial tools. Show your work and explain your reasoning."""
238
+
239
+ # Get response from agent
240
+ response = self.agent.run(enhanced_question)
241
+ return response
242
+
243
+ except Exception as e:
244
+ error_msg = f"Error processing query: {str(e)}"
245
+ print(error_msg)
246
+ return error_msg
247
+
248
+ def get_conversation_history(self) -> List[Any]:
249
+ """Get the current conversation history."""
250
+ if self.memory:
251
+ return self.memory.chat_memory.messages
252
+ return []
253
+
254
+ def clear_memory(self):
255
+ """Clear the conversation memory."""
256
+ if self.memory:
257
+ self.memory.clear()
258
+ print("✅ Conversation memory cleared")
259
+
260
+ def get_available_tools(self) -> List[str]:
261
+ """Get list of available tool names."""
262
+ return [tool.name for tool in self.tools]
263
+
264
+ def health_check(self) -> dict:
265
+ """Perform a health check of the agent components."""
266
+ status = {
267
+ "model_loaded": self.model is not None,
268
+ "llm_ready": self.llm is not None,
269
+ "agent_ready": self.agent is not None,
270
+ "memory_ready": self.memory is not None,
271
+ "tools_count": len(self.tools),
272
+ "device": "cuda" if torch.cuda.is_available() else "cpu"
273
+ }
274
+ return status
275
+
276
+
277
+ def create_cfa_agent(model_name: str = "tarun7r/Finance-Llama-8B") -> CFAAgent:
278
+ """
279
+ Factory function to create and return a CFA Agent instance.
280
+
281
+ Args:
282
+ model_name: Hugging Face model name for financial analysis
283
+
284
+ Returns:
285
+ Initialized CFAAgent instance
286
+ """
287
+ try:
288
+ agent = CFAAgent(model_name=model_name)
289
+ print("🎯 CFA Agent created successfully")
290
+ return agent
291
+ except Exception as e:
292
+ print(f"❌ Failed to create CFA Agent: {str(e)}")
293
+ raise
294
+
295
+
296
+ # Example usage and testing
297
+ if __name__ == "__main__":
298
+ print("🚀 Initializing CFA AI Agent...")
299
+
300
+ try:
301
+ # Create agent
302
+ cfa_agent = create_cfa_agent()
303
+
304
+ # Health check
305
+ health = cfa_agent.health_check()
306
+ print("📊 Health Check Results:")
307
+ for key, value in health.items():
308
+ status = "✅" if value else "❌"
309
+ print(f" {status} {key}: {value}")
310
+
311
+ # Test queries
312
+ test_queries = [
313
+ "What is the current stock price of Apple (AAPL)?",
314
+ "Calculate the PE ratio comparison between Apple and Microsoft",
315
+ "Explain the CAPM model in simple terms"
316
+ ]
317
+
318
+ print("\n🧪 Running test queries...")
319
+ for i, query in enumerate(test_queries, 1):
320
+ print(f"\n--- Test Query {i} ---")
321
+ print(f"Q: {query}")
322
+ try:
323
+ response = cfa_agent.query(query)
324
+ print(f"A: {response}")
325
+ except Exception as e:
326
+ print(f"❌ Query failed: {str(e)}")
327
+
328
+ except Exception as e:
329
+ print(f"❌ CFA Agent initialization failed: {str(e)}")
app.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CFA AI Agent - Chainlit Chat Interface
3
+ Main application file for the CFA AI Agent web interface.
4
+ """
5
+
6
+ import chainlit as cl
7
+ import pandas as pd
8
+ import numpy as np
9
+ import plotly.graph_objects as go
10
+ import plotly.express as px
11
+ from datetime import datetime, timedelta
12
+ import sys
13
+ import os
14
+ import asyncio
15
+
16
+ # Add the current directory to Python path for imports
17
+ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
18
+
19
+ try:
20
+ from agent import create_cfa_agent, CFAAgent
21
+ from tools.data_fetcher import get_stock_price, get_market_indices
22
+ except ImportError as e:
23
+ print(f"Import error: {e}")
24
+ raise
25
+
26
+
27
+ # Global agent instance
28
+ agent = None
29
+
30
+
31
+ @cl.on_chat_start
32
+ async def start():
33
+ """Initialize the CFA agent when chat starts."""
34
+ global agent
35
+
36
+ await cl.Message(
37
+ content="🤖 **Welcome to CFA AI Agent!**\n\nI'm your professional financial analysis assistant powered by Finance-Llama-8B and real-time market data.\n\n**What I can help you with:**\n\n📊 **Market Data & Analysis:**\n• Real-time stock prices and company information\n• Historical data analysis and trends\n• Market indices and sector performance\n• Stock comparisons and valuations\n\n🧮 **Financial Calculations:**\n• DCF (Discounted Cash Flow) valuations\n• Risk metrics (Sharpe ratio, Beta, volatility)\n• Financial ratios analysis\n• WACC and cost of capital calculations\n\n📚 **CFA Knowledge:**\n• Investment concepts and theories\n• Portfolio management principles\n• Financial statement analysis\n• Derivatives and fixed income\n\n**Example queries:**\n• \"What is the current stock price of Apple (AAPL)?\"\n• \"Perform a DCF valuation for Tesla with 10% growth rate\"\n• \"Explain the CAPM model with examples\"\n• \"Compare the PE ratios of Apple and Microsoft\"\n\nInitializing the agent... This may take a moment on first run."
38
+ ).send()
39
+
40
+ try:
41
+ # Show loading message
42
+ loading_msg = cl.Message(content="🔄 Loading CFA AI Agent...")
43
+ await loading_msg.send()
44
+
45
+ # Initialize the agent
46
+ agent = create_cfa_agent()
47
+
48
+ # Update loading message
49
+ loading_msg.content = "✅ **CFA AI Agent is ready!**\n\nYou can now ask me anything about finance, stocks, valuations, or CFA topics. Try asking about a specific stock or financial concept!"
50
+ await loading_msg.update()
51
+
52
+ # Add market overview as an action
53
+ await display_market_overview()
54
+
55
+ # Add action buttons
56
+ actions = [
57
+ cl.Action(name="market_summary", value="market_summary", label="📈 Market Summary"),
58
+ cl.Action(name="stock_analysis", value="stock_analysis", label="🔍 Stock Analysis"),
59
+ cl.Action(name="cfa_concept", value="cfa_concept", label="📚 CFA Concept"),
60
+ cl.Action(name="clear_memory", value="clear_memory", label="🗑️ Clear History")
61
+ ]
62
+ await cl.Message(content="**Quick Actions:**", actions=actions).send()
63
+
64
+ except Exception as e:
65
+ error_msg = f"❌ **Failed to initialize CFA Agent:** {str(e)}\n\nPlease check your setup and try again."
66
+ await cl.Message(content=error_msg).send()
67
+
68
+
69
+ async def display_market_overview():
70
+ """Display market overview as an action."""
71
+ try:
72
+ indices_data = get_market_indices()
73
+
74
+ if "error" not in indices_data:
75
+ market_content = "📊 **Market Overview:**\n\n"
76
+ for name, data in indices_data.items():
77
+ if "error" not in data:
78
+ change_emoji = "🟢" if data["change"] >= 0 else "🔴"
79
+ market_content += f"{change_emoji} **{name}:** {data['current_value']:,.2f} "
80
+ market_content += f"({data['change']:+.2f}, {data['change_percent']:+.2f}%)\n"
81
+
82
+ await cl.Message(content=market_content).send()
83
+ except Exception as e:
84
+ await cl.Message(content=f"⚠️ Unable to load market data: {str(e)}").send()
85
+
86
+
87
+ @cl.on_message
88
+ async def main(message: cl.Message):
89
+ """Handle incoming messages."""
90
+ global agent
91
+
92
+ if not agent:
93
+ await cl.Message(content="❌ Agent not initialized. Please restart the chat.").send()
94
+ return
95
+
96
+ # Show thinking message
97
+ thinking_msg = cl.Message(content="🤔 Analyzing your query...")
98
+ await thinking_msg.send()
99
+
100
+ try:
101
+ # Get agent response
102
+ response = agent.query(message.content)
103
+
104
+ # Format the response
105
+ formatted_response = format_agent_response(response)
106
+
107
+ # Update the thinking message with the response
108
+ thinking_msg.content = formatted_response
109
+ await thinking_msg.update()
110
+
111
+ except Exception as e:
112
+ error_response = f"❌ **Error processing your query:** {str(e)}\n\nPlease try rephrasing your question or check if the requested data is available."
113
+ thinking_msg.content = error_response
114
+ await thinking_msg.update()
115
+
116
+
117
+ def format_agent_response(response):
118
+ """Format the agent response for better display."""
119
+ if isinstance(response, dict) and "error" in response:
120
+ return f"❌ **Error:** {response['error']}"
121
+
122
+ # Convert to string if needed
123
+ response_str = str(response)
124
+
125
+ # Add some basic formatting for common patterns
126
+ response_str = response_str.replace("Thought:", "\n**🤔 Thought:**")
127
+ response_str = response_str.replace("Action:", "\n**⚡ Action:**")
128
+ response_str = response_str.replace("Action Input:", "\n**📝 Action Input:**")
129
+ response_str = response_str.replace("Observation:", "\n**👀 Observation:**")
130
+ response_str = response_str.replace("Final Answer:", "\n**✅ Final Answer:**")
131
+
132
+ return response_str
133
+
134
+
135
+ @cl.action_callback("market_summary")
136
+ async def market_summary():
137
+ """Handle market summary action."""
138
+ await main(cl.Message(content="Give me a summary of today's market performance"))
139
+
140
+
141
+ @cl.action_callback("stock_analysis")
142
+ async def stock_analysis():
143
+ """Handle stock analysis action."""
144
+ await cl.AskUserMessage(content="Please enter a stock ticker symbol for analysis:", timeout=30).send()
145
+
146
+
147
+ @cl.action_callback("cfa_concept")
148
+ async def cfa_concept():
149
+ """Handle CFA concept explanation action."""
150
+ await main(cl.Message(content="Explain a key CFA concept with examples"))
151
+
152
+
153
+ @cl.action_callback("clear_memory")
154
+ async def clear_memory():
155
+ """Handle clear memory action."""
156
+ global agent
157
+ if agent:
158
+ agent.clear_memory()
159
+ await cl.Message(content="🗑️ **Chat history cleared!** You can start a fresh conversation.").send()
160
+
161
+
162
+
163
+
164
+ if __name__ == "__main__":
165
+ # Chainlit apps are run with `chainlit run app.py`
166
+ pass
requirements.txt ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core ML and LLM dependencies - optimized for HF Spaces
2
+ torch>=2.0.0,<2.5.0
3
+ transformers>=4.35.0,<5.0.0
4
+ accelerate>=0.24.0
5
+ tokenizers>=0.15.0
6
+ bitsandbytes
7
+
8
+ # LangChain ecosystem
9
+ langchain>=0.1.0,<0.3.0
10
+ langchain-community>=0.0.10
11
+ langchain-core>=0.1.0
12
+
13
+ # Financial data and analysis
14
+ yfinance>=0.2.18
15
+ pandas>=1.5.0,<3.0.0
16
+ numpy>=1.24.0,<2.0.0
17
+
18
+ # Web interface for HF Spaces
19
+ chainlit>=1.0.0
20
+ plotly>=5.15.0
21
+ gradio>=4.0.0
22
+
23
+ # Additional utilities
24
+ requests>=2.31.0
25
+ python-dotenv>=1.0.0
26
+
27
+ # HF Spaces optimization
28
+ huggingface_hub>=0.20.0
tools/__pycache__/data_fetcher.cpython-311.pyc ADDED
Binary file (20 kB). View file
 
tools/__pycache__/finance_tools.cpython-311.pyc ADDED
Binary file (13.5 kB). View file
 
tools/data_fetcher.py ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CFA AI Agent - Real-time Financial Data Fetcher
3
+ This module handles fetching real-time financial data using yfinance.
4
+ """
5
+
6
+ import yfinance as yf
7
+ import pandas as pd
8
+ import numpy as np
9
+ from typing import Dict, List, Optional, Union
10
+ from datetime import datetime, timedelta
11
+ from langchain.tools import tool
12
+
13
+
14
+ @tool
15
+ def get_stock_price(ticker: str) -> Dict[str, Union[float, str]]:
16
+ """
17
+ Get current stock price and basic information.
18
+
19
+ Args:
20
+ ticker: Stock ticker symbol
21
+
22
+ Returns:
23
+ Dictionary with current price and market data
24
+ """
25
+ try:
26
+ stock = yf.Ticker(ticker.upper())
27
+ info = stock.info
28
+
29
+ # Get latest price data
30
+ hist = stock.history(period="1d")
31
+ if hist.empty:
32
+ raise ValueError(f"No data available for ticker {ticker}")
33
+
34
+ current_price = hist['Close'].iloc[-1]
35
+ previous_close = info.get('previousClose', current_price)
36
+ change = current_price - previous_close
37
+ change_percent = (change / previous_close) * 100 if previous_close != 0 else 0
38
+
39
+ return {
40
+ "ticker": ticker.upper(),
41
+ "company_name": info.get('longName', 'Unknown'),
42
+ "current_price": round(current_price, 2),
43
+ "previous_close": round(previous_close, 2),
44
+ "change": round(change, 2),
45
+ "change_percent": round(change_percent, 2),
46
+ "volume": hist['Volume'].iloc[-1],
47
+ "market_cap": info.get('marketCap'),
48
+ "currency": info.get('currency', 'USD'),
49
+ "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
50
+ }
51
+ except Exception as e:
52
+ return {"error": f"Failed to fetch stock price for {ticker}: {str(e)}"}
53
+
54
+
55
+ @tool
56
+ def get_historical_data(
57
+ ticker: str,
58
+ period: str = "1y",
59
+ interval: str = "1d"
60
+ ) -> Dict[str, Union[List, str]]:
61
+ """
62
+ Get historical stock data for analysis.
63
+
64
+ Args:
65
+ ticker: Stock ticker symbol
66
+ period: Time period (1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max)
67
+ interval: Data interval (1m, 2m, 5m, 15m, 30m, 60m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo)
68
+
69
+ Returns:
70
+ Dictionary with historical price data and statistics
71
+ """
72
+ try:
73
+ stock = yf.Ticker(ticker.upper())
74
+ hist = stock.history(period=period, interval=interval)
75
+
76
+ if hist.empty:
77
+ raise ValueError(f"No historical data available for {ticker}")
78
+
79
+ # Calculate basic statistics
80
+ returns = hist['Close'].pct_change().dropna()
81
+
82
+ stats = {
83
+ "ticker": ticker.upper(),
84
+ "period": period,
85
+ "interval": interval,
86
+ "data_points": len(hist),
87
+ "start_date": hist.index[0].strftime("%Y-%m-%d"),
88
+ "end_date": hist.index[-1].strftime("%Y-%m-%d"),
89
+
90
+ # Price statistics
91
+ "highest_price": round(hist['High'].max(), 2),
92
+ "lowest_price": round(hist['Low'].min(), 2),
93
+ "avg_price": round(hist['Close'].mean(), 2),
94
+ "current_price": round(hist['Close'].iloc[-1], 2),
95
+
96
+ # Return statistics
97
+ "total_return": round(((hist['Close'].iloc[-1] / hist['Close'].iloc[0]) - 1) * 100, 2),
98
+ "volatility": round(returns.std() * np.sqrt(252) * 100, 2), # Annualized volatility
99
+ "avg_daily_return": round(returns.mean() * 100, 4),
100
+ "max_daily_gain": round(returns.max() * 100, 2),
101
+ "max_daily_loss": round(returns.min() * 100, 2),
102
+
103
+ # Volume statistics
104
+ "avg_volume": int(hist['Volume'].mean()),
105
+ "max_volume": int(hist['Volume'].max()),
106
+ "min_volume": int(hist['Volume'].min()),
107
+
108
+ # Recent data (last 5 days)
109
+ "recent_prices": hist['Close'].tail(5).round(2).tolist(),
110
+ "recent_dates": [date.strftime("%Y-%m-%d") for date in hist.index[-5:]],
111
+ "recent_volumes": hist['Volume'].tail(5).tolist()
112
+ }
113
+
114
+ return stats
115
+ except Exception as e:
116
+ return {"error": f"Failed to fetch historical data for {ticker}: {str(e)}"}
117
+
118
+
119
+ @tool
120
+ def get_company_info(ticker: str) -> Dict[str, Union[str, float, int]]:
121
+ """
122
+ Get comprehensive company information and fundamentals.
123
+
124
+ Args:
125
+ ticker: Stock ticker symbol
126
+
127
+ Returns:
128
+ Dictionary with company information and key metrics
129
+ """
130
+ try:
131
+ stock = yf.Ticker(ticker.upper())
132
+ info = stock.info
133
+
134
+ company_data = {
135
+ "ticker": ticker.upper(),
136
+ "company_name": info.get('longName', 'Unknown'),
137
+ "sector": info.get('sector', 'Unknown'),
138
+ "industry": info.get('industry', 'Unknown'),
139
+ "country": info.get('country', 'Unknown'),
140
+ "website": info.get('website', 'N/A'),
141
+ "business_summary": info.get('longBusinessSummary', 'N/A'),
142
+
143
+ # Key executives
144
+ "ceo": info.get('companyOfficers', [{}])[0].get('name', 'N/A') if info.get('companyOfficers') else 'N/A',
145
+
146
+ # Financial metrics
147
+ "market_cap": info.get('marketCap'),
148
+ "enterprise_value": info.get('enterpriseValue'),
149
+ "shares_outstanding": info.get('sharesOutstanding'),
150
+ "float_shares": info.get('floatShares'),
151
+
152
+ # Employee info
153
+ "full_time_employees": info.get('fullTimeEmployees'),
154
+
155
+ # Exchange info
156
+ "exchange": info.get('exchange', 'Unknown'),
157
+ "quote_type": info.get('quoteType', 'Unknown'),
158
+ "currency": info.get('currency', 'USD'),
159
+
160
+ # ESG scores (if available)
161
+ "esg_scores": info.get('esgScores'),
162
+ "sustainability_score": info.get('sustainabilityScore'),
163
+
164
+ # Analyst recommendations
165
+ "recommendation": info.get('recommendationKey', 'N/A'),
166
+ "target_high_price": info.get('targetHighPrice'),
167
+ "target_low_price": info.get('targetLowPrice'),
168
+ "target_mean_price": info.get('targetMeanPrice'),
169
+ "number_of_analyst_opinions": info.get('numberOfAnalystOpinions'),
170
+
171
+ # Risk metrics
172
+ "audit_risk": info.get('auditRisk'),
173
+ "board_risk": info.get('boardRisk'),
174
+ "compensation_risk": info.get('compensationRisk'),
175
+ "shareholder_rights_risk": info.get('shareHolderRightsRisk'),
176
+ "overall_risk": info.get('overallRisk')
177
+ }
178
+
179
+ # Remove None values
180
+ company_data = {k: v for k, v in company_data.items() if v is not None}
181
+
182
+ return company_data
183
+ except Exception as e:
184
+ return {"error": f"Failed to fetch company info for {ticker}: {str(e)}"}
185
+
186
+
187
+ @tool
188
+ def get_financial_statements(ticker: str) -> Dict[str, Union[pd.DataFrame, str]]:
189
+ """
190
+ Get financial statements (income statement, balance sheet, cash flow).
191
+
192
+ Args:
193
+ ticker: Stock ticker symbol
194
+
195
+ Returns:
196
+ Dictionary with financial statement data
197
+ """
198
+ try:
199
+ stock = yf.Ticker(ticker.upper())
200
+
201
+ # Fetch financial statements
202
+ income_stmt = stock.financials
203
+ balance_sheet = stock.balance_sheet
204
+ cash_flow = stock.cashflow
205
+
206
+ result = {
207
+ "ticker": ticker.upper(),
208
+ "has_income_statement": not income_stmt.empty,
209
+ "has_balance_sheet": not balance_sheet.empty,
210
+ "has_cash_flow": not cash_flow.empty,
211
+ }
212
+
213
+ # Convert to dictionaries for easier handling
214
+ if not income_stmt.empty:
215
+ result["income_statement_years"] = [str(col.year) for col in income_stmt.columns]
216
+ result["total_revenue"] = income_stmt.loc['Total Revenue'].to_dict() if 'Total Revenue' in income_stmt.index else {}
217
+ result["net_income"] = income_stmt.loc['Net Income'].to_dict() if 'Net Income' in income_stmt.index else {}
218
+
219
+ if not balance_sheet.empty:
220
+ result["balance_sheet_years"] = [str(col.year) for col in balance_sheet.columns]
221
+ result["total_assets"] = balance_sheet.loc['Total Assets'].to_dict() if 'Total Assets' in balance_sheet.index else {}
222
+ result["total_debt"] = balance_sheet.loc['Total Debt'].to_dict() if 'Total Debt' in balance_sheet.index else {}
223
+
224
+ if not cash_flow.empty:
225
+ result["cash_flow_years"] = [str(col.year) for col in cash_flow.columns]
226
+ result["operating_cash_flow"] = cash_flow.loc['Operating Cash Flow'].to_dict() if 'Operating Cash Flow' in cash_flow.index else {}
227
+ result["free_cash_flow"] = cash_flow.loc['Free Cash Flow'].to_dict() if 'Free Cash Flow' in cash_flow.index else {}
228
+
229
+ return result
230
+ except Exception as e:
231
+ return {"error": f"Failed to fetch financial statements for {ticker}: {str(e)}"}
232
+
233
+
234
+ @tool
235
+ def get_market_indices() -> Dict[str, Dict[str, Union[float, str]]]:
236
+ """
237
+ Get current prices and performance of major market indices.
238
+
239
+ Returns:
240
+ Dictionary with major market index data
241
+ """
242
+ try:
243
+ indices = {
244
+ "S&P 500": "^GSPC",
245
+ "Dow Jones": "^DJI",
246
+ "NASDAQ": "^IXIC",
247
+ "Russell 2000": "^RUT",
248
+ "VIX": "^VIX",
249
+ "10-Year Treasury": "^TNX"
250
+ }
251
+
252
+ results = {}
253
+ for name, ticker in indices.items():
254
+ try:
255
+ index = yf.Ticker(ticker)
256
+ hist = index.history(period="2d")
257
+ if not hist.empty:
258
+ current_price = hist['Close'].iloc[-1]
259
+ previous_close = hist['Close'].iloc[-2] if len(hist) > 1 else current_price
260
+ change = current_price - previous_close
261
+ change_percent = (change / previous_close) * 100 if previous_close != 0 else 0
262
+
263
+ results[name] = {
264
+ "ticker": ticker,
265
+ "current_value": round(current_price, 2),
266
+ "change": round(change, 2),
267
+ "change_percent": round(change_percent, 2),
268
+ "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
269
+ }
270
+ except Exception:
271
+ results[name] = {"error": f"Failed to fetch data for {name}"}
272
+
273
+ return results
274
+ except Exception as e:
275
+ return {"error": f"Failed to fetch market indices: {str(e)}"}
276
+
277
+
278
+ @tool
279
+ def compare_stocks(tickers: List[str], metric: str = "performance") -> Dict[str, Union[List, str]]:
280
+ """
281
+ Compare multiple stocks on various metrics.
282
+
283
+ Args:
284
+ tickers: List of stock ticker symbols
285
+ metric: Comparison metric ('performance', 'valuation', 'volatility')
286
+
287
+ Returns:
288
+ Dictionary with comparison results
289
+ """
290
+ try:
291
+ if len(tickers) < 2:
292
+ raise ValueError("Need at least 2 tickers for comparison")
293
+
294
+ results = {
295
+ "tickers": [t.upper() for t in tickers],
296
+ "metric": metric,
297
+ "comparison_data": {}
298
+ }
299
+
300
+ for ticker in tickers:
301
+ try:
302
+ stock = yf.Ticker(ticker.upper())
303
+ info = stock.info
304
+ hist = stock.history(period="1y")
305
+
306
+ if metric == "performance":
307
+ if not hist.empty:
308
+ ytd_return = ((hist['Close'].iloc[-1] / hist['Close'].iloc[0]) - 1) * 100
309
+ results["comparison_data"][ticker.upper()] = {
310
+ "ytd_return": round(ytd_return, 2),
311
+ "current_price": round(hist['Close'].iloc[-1], 2),
312
+ "52_week_high": info.get('fiftyTwoWeekHigh'),
313
+ "52_week_low": info.get('fiftyTwoWeekLow')
314
+ }
315
+
316
+ elif metric == "valuation":
317
+ results["comparison_data"][ticker.upper()] = {
318
+ "pe_ratio": info.get('trailingPE'),
319
+ "price_to_book": info.get('priceToBook'),
320
+ "price_to_sales": info.get('priceToSalesTrailing12Months'),
321
+ "market_cap": info.get('marketCap')
322
+ }
323
+
324
+ elif metric == "volatility":
325
+ if not hist.empty:
326
+ returns = hist['Close'].pct_change().dropna()
327
+ volatility = returns.std() * np.sqrt(252) * 100
328
+ results["comparison_data"][ticker.upper()] = {
329
+ "volatility": round(volatility, 2),
330
+ "beta": info.get('beta'),
331
+ "max_drawdown": round((hist['Close'].min() / hist['Close'].max() - 1) * 100, 2)
332
+ }
333
+
334
+ except Exception as e:
335
+ results["comparison_data"][ticker.upper()] = {"error": str(e)}
336
+
337
+ return results
338
+ except Exception as e:
339
+ return {"error": f"Stock comparison failed: {str(e)}"}
tools/finance_tools.py ADDED
@@ -0,0 +1,323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CFA AI Agent - Finance Calculation Tools
3
+ This module contains various financial calculation functions for CFA analysis.
4
+ """
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+ from typing import List, Dict, Union, Optional
9
+ import yfinance as yf
10
+ from langchain.tools import tool
11
+
12
+
13
+ @tool
14
+ def calculate_dcf(
15
+ cash_flows: List[float],
16
+ terminal_value: float,
17
+ discount_rate: float
18
+ ) -> Dict[str, float]:
19
+ """
20
+ Calculate Discounted Cash Flow (DCF) valuation.
21
+
22
+ Args:
23
+ cash_flows: List of projected free cash flows
24
+ terminal_value: Terminal value at end of projection period
25
+ discount_rate: Weighted average cost of capital (WACC) as decimal
26
+
27
+ Returns:
28
+ Dictionary with NPV, terminal value present value, and total enterprise value
29
+ """
30
+ try:
31
+ if not cash_flows:
32
+ raise ValueError("Cash flows list cannot be empty")
33
+ if discount_rate <= 0:
34
+ raise ValueError("Discount rate must be positive")
35
+
36
+ # Calculate present value of cash flows
37
+ pv_cash_flows = []
38
+ for i, cf in enumerate(cash_flows, 1):
39
+ pv = cf / ((1 + discount_rate) ** i)
40
+ pv_cash_flows.append(pv)
41
+
42
+ # Calculate present value of terminal value
43
+ years = len(cash_flows)
44
+ pv_terminal = terminal_value / ((1 + discount_rate) ** years)
45
+
46
+ # Total enterprise value
47
+ enterprise_value = sum(pv_cash_flows) + pv_terminal
48
+
49
+ return {
50
+ "pv_cash_flows": sum(pv_cash_flows),
51
+ "pv_terminal_value": pv_terminal,
52
+ "enterprise_value": enterprise_value,
53
+ "cash_flow_details": pv_cash_flows
54
+ }
55
+ except Exception as e:
56
+ return {"error": f"DCF calculation failed: {str(e)}"}
57
+
58
+
59
+ @tool
60
+ def calculate_sharpe_ratio(
61
+ returns: List[float],
62
+ risk_free_rate: float
63
+ ) -> Dict[str, float]:
64
+ """
65
+ Calculate Sharpe Ratio for risk-adjusted returns.
66
+
67
+ Args:
68
+ returns: List of periodic returns (as decimals)
69
+ risk_free_rate: Risk-free rate (as decimal)
70
+
71
+ Returns:
72
+ Dictionary with Sharpe ratio, average return, and standard deviation
73
+ """
74
+ try:
75
+ if not returns:
76
+ raise ValueError("Returns list cannot be empty")
77
+ if len(returns) < 2:
78
+ raise ValueError("Need at least 2 return observations")
79
+
80
+ returns_array = np.array(returns)
81
+
82
+ # Calculate metrics
83
+ avg_return = np.mean(returns_array)
84
+ std_dev = np.std(returns_array, ddof=1) # Sample standard deviation
85
+ excess_return = avg_return - risk_free_rate
86
+
87
+ if std_dev == 0:
88
+ raise ValueError("Standard deviation cannot be zero")
89
+
90
+ sharpe_ratio = excess_return / std_dev
91
+
92
+ return {
93
+ "sharpe_ratio": sharpe_ratio,
94
+ "average_return": avg_return,
95
+ "standard_deviation": std_dev,
96
+ "excess_return": excess_return,
97
+ "risk_free_rate": risk_free_rate
98
+ }
99
+ except Exception as e:
100
+ return {"error": f"Sharpe ratio calculation failed: {str(e)}"}
101
+
102
+
103
+ @tool
104
+ def compare_pe_ratios(ticker1: str, ticker2: str) -> Dict[str, Union[float, str]]:
105
+ """
106
+ Compare P/E ratios of two stocks using real-time data.
107
+
108
+ Args:
109
+ ticker1: First stock ticker symbol
110
+ ticker2: Second stock ticker symbol
111
+
112
+ Returns:
113
+ Dictionary with P/E ratios and comparison analysis
114
+ """
115
+ try:
116
+ # Fetch stock data
117
+ stock1 = yf.Ticker(ticker1.upper())
118
+ stock2 = yf.Ticker(ticker2.upper())
119
+
120
+ # Get info
121
+ info1 = stock1.info
122
+ info2 = stock2.info
123
+
124
+ # Extract P/E ratios
125
+ pe1 = info1.get('trailingPE') or info1.get('forwardPE')
126
+ pe2 = info2.get('trailingPE') or info2.get('forwardPE')
127
+
128
+ if pe1 is None or pe2 is None:
129
+ return {"error": f"Could not retrieve P/E ratios for {ticker1} or {ticker2}"}
130
+
131
+ # Calculate comparison metrics
132
+ pe_difference = pe1 - pe2
133
+ pe_ratio = pe1 / pe2 if pe2 != 0 else None
134
+
135
+ # Determine which is more expensive
136
+ comparison = "equal"
137
+ if pe1 > pe2:
138
+ comparison = f"{ticker1} is more expensive"
139
+ elif pe1 < pe2:
140
+ comparison = f"{ticker2} is more expensive"
141
+
142
+ return {
143
+ f"{ticker1}_pe": pe1,
144
+ f"{ticker2}_pe": pe2,
145
+ "pe_difference": pe_difference,
146
+ "pe_ratio": pe_ratio,
147
+ "comparison": comparison,
148
+ f"{ticker1}_name": info1.get('longName', ticker1),
149
+ f"{ticker2}_name": info2.get('longName', ticker2)
150
+ }
151
+ except Exception as e:
152
+ return {"error": f"P/E comparison failed: {str(e)}"}
153
+
154
+
155
+ @tool
156
+ def calculate_beta(ticker: str, market_ticker: str = "^GSPC", period: str = "2y") -> Dict[str, float]:
157
+ """
158
+ Calculate beta coefficient for a stock relative to market.
159
+
160
+ Args:
161
+ ticker: Stock ticker symbol
162
+ market_ticker: Market index ticker (default S&P 500)
163
+ period: Time period for calculation
164
+
165
+ Returns:
166
+ Dictionary with beta, correlation, and other metrics
167
+ """
168
+ try:
169
+ # Fetch data
170
+ stock = yf.Ticker(ticker.upper())
171
+ market = yf.Ticker(market_ticker)
172
+
173
+ # Get historical data
174
+ stock_data = stock.history(period=period)
175
+ market_data = market.history(period=period)
176
+
177
+ if stock_data.empty or market_data.empty:
178
+ raise ValueError("Could not fetch historical data")
179
+
180
+ # Calculate returns
181
+ stock_returns = stock_data['Close'].pct_change().dropna()
182
+ market_returns = market_data['Close'].pct_change().dropna()
183
+
184
+ # Align data
185
+ aligned_data = pd.concat([stock_returns, market_returns], axis=1, join='inner')
186
+ aligned_data.columns = ['stock', 'market']
187
+ aligned_data = aligned_data.dropna()
188
+
189
+ if len(aligned_data) < 20:
190
+ raise ValueError("Insufficient data points for beta calculation")
191
+
192
+ # Calculate beta
193
+ covariance = np.cov(aligned_data['stock'], aligned_data['market'])[0][1]
194
+ market_variance = np.var(aligned_data['market'], ddof=1)
195
+ beta = covariance / market_variance
196
+
197
+ # Calculate correlation
198
+ correlation = np.corrcoef(aligned_data['stock'], aligned_data['market'])[0][1]
199
+
200
+ return {
201
+ "beta": beta,
202
+ "correlation": correlation,
203
+ "stock_volatility": np.std(aligned_data['stock'], ddof=1),
204
+ "market_volatility": np.std(aligned_data['market'], ddof=1),
205
+ "data_points": len(aligned_data),
206
+ "period": period
207
+ }
208
+ except Exception as e:
209
+ return {"error": f"Beta calculation failed: {str(e)}"}
210
+
211
+
212
+ @tool
213
+ def calculate_wacc(
214
+ cost_of_equity: float,
215
+ cost_of_debt: float,
216
+ tax_rate: float,
217
+ market_value_equity: float,
218
+ market_value_debt: float
219
+ ) -> Dict[str, float]:
220
+ """
221
+ Calculate Weighted Average Cost of Capital (WACC).
222
+
223
+ Args:
224
+ cost_of_equity: Cost of equity as decimal
225
+ cost_of_debt: Cost of debt as decimal
226
+ tax_rate: Corporate tax rate as decimal
227
+ market_value_equity: Market value of equity
228
+ market_value_debt: Market value of debt
229
+
230
+ Returns:
231
+ Dictionary with WACC and component calculations
232
+ """
233
+ try:
234
+ total_value = market_value_equity + market_value_debt
235
+
236
+ if total_value <= 0:
237
+ raise ValueError("Total market value must be positive")
238
+
239
+ # Calculate weights
240
+ weight_equity = market_value_equity / total_value
241
+ weight_debt = market_value_debt / total_value
242
+
243
+ # Calculate after-tax cost of debt
244
+ after_tax_cost_debt = cost_of_debt * (1 - tax_rate)
245
+
246
+ # Calculate WACC
247
+ wacc = (weight_equity * cost_of_equity) + (weight_debt * after_tax_cost_debt)
248
+
249
+ return {
250
+ "wacc": wacc,
251
+ "weight_equity": weight_equity,
252
+ "weight_debt": weight_debt,
253
+ "after_tax_cost_debt": after_tax_cost_debt,
254
+ "cost_of_equity": cost_of_equity,
255
+ "cost_of_debt": cost_of_debt,
256
+ "tax_rate": tax_rate
257
+ }
258
+ except Exception as e:
259
+ return {"error": f"WACC calculation failed: {str(e)}"}
260
+
261
+
262
+ @tool
263
+ def financial_ratios_analysis(ticker: str) -> Dict[str, Union[float, str]]:
264
+ """
265
+ Perform comprehensive financial ratios analysis for a stock.
266
+
267
+ Args:
268
+ ticker: Stock ticker symbol
269
+
270
+ Returns:
271
+ Dictionary with various financial ratios and metrics
272
+ """
273
+ try:
274
+ stock = yf.Ticker(ticker.upper())
275
+ info = stock.info
276
+
277
+ # Extract key metrics
278
+ ratios = {
279
+ "ticker": ticker.upper(),
280
+ "company_name": info.get('longName', 'N/A'),
281
+
282
+ # Valuation ratios
283
+ "pe_ratio": info.get('trailingPE'),
284
+ "forward_pe": info.get('forwardPE'),
285
+ "price_to_book": info.get('priceToBook'),
286
+ "price_to_sales": info.get('priceToSalesTrailing12Months'),
287
+ "peg_ratio": info.get('pegRatio'),
288
+
289
+ # Profitability ratios
290
+ "profit_margin": info.get('profitMargins'),
291
+ "operating_margin": info.get('operatingMargins'),
292
+ "roe": info.get('returnOnEquity'),
293
+ "roa": info.get('returnOnAssets'),
294
+
295
+ # Financial health
296
+ "current_ratio": info.get('currentRatio'),
297
+ "quick_ratio": info.get('quickRatio'),
298
+ "debt_to_equity": info.get('debtToEquity'),
299
+ "total_debt": info.get('totalDebt'),
300
+ "total_cash": info.get('totalCash'),
301
+
302
+ # Market data
303
+ "market_cap": info.get('marketCap'),
304
+ "enterprise_value": info.get('enterpriseValue'),
305
+ "beta": info.get('beta'),
306
+ "52_week_high": info.get('fiftyTwoWeekHigh'),
307
+ "52_week_low": info.get('fiftyTwoWeekLow'),
308
+
309
+ # Dividend info
310
+ "dividend_yield": info.get('dividendYield'),
311
+ "payout_ratio": info.get('payoutRatio'),
312
+
313
+ # Growth metrics
314
+ "earnings_growth": info.get('earningsGrowth'),
315
+ "revenue_growth": info.get('revenueGrowth')
316
+ }
317
+
318
+ # Remove None values
319
+ ratios = {k: v for k, v in ratios.items() if v is not None}
320
+
321
+ return ratios
322
+ except Exception as e:
323
+ return {"error": f"Financial ratios analysis failed: {str(e)}"}