quantalogic 0.35.0__py3-none-any.whl → 0.40.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. quantalogic/__init__.py +0 -4
  2. quantalogic/agent.py +603 -363
  3. quantalogic/agent_config.py +233 -46
  4. quantalogic/agent_factory.py +34 -22
  5. quantalogic/coding_agent.py +16 -14
  6. quantalogic/config.py +2 -1
  7. quantalogic/console_print_events.py +4 -8
  8. quantalogic/console_print_token.py +2 -2
  9. quantalogic/docs_cli.py +15 -10
  10. quantalogic/event_emitter.py +258 -83
  11. quantalogic/flow/__init__.py +23 -0
  12. quantalogic/flow/flow.py +595 -0
  13. quantalogic/flow/flow_extractor.py +672 -0
  14. quantalogic/flow/flow_generator.py +89 -0
  15. quantalogic/flow/flow_manager.py +407 -0
  16. quantalogic/flow/flow_manager_schema.py +169 -0
  17. quantalogic/flow/flow_yaml.md +419 -0
  18. quantalogic/generative_model.py +109 -77
  19. quantalogic/get_model_info.py +5 -5
  20. quantalogic/interactive_text_editor.py +100 -73
  21. quantalogic/main.py +17 -21
  22. quantalogic/model_info_list.py +3 -3
  23. quantalogic/model_info_litellm.py +14 -14
  24. quantalogic/prompts.py +2 -1
  25. quantalogic/{llm.py → quantlitellm.py} +29 -39
  26. quantalogic/search_agent.py +4 -4
  27. quantalogic/server/models.py +4 -1
  28. quantalogic/task_file_reader.py +5 -5
  29. quantalogic/task_runner.py +20 -20
  30. quantalogic/tool_manager.py +10 -21
  31. quantalogic/tools/__init__.py +98 -68
  32. quantalogic/tools/composio/composio.py +416 -0
  33. quantalogic/tools/{generate_database_report_tool.py → database/generate_database_report_tool.py} +4 -9
  34. quantalogic/tools/database/sql_query_tool_advanced.py +261 -0
  35. quantalogic/tools/document_tools/markdown_to_docx_tool.py +620 -0
  36. quantalogic/tools/document_tools/markdown_to_epub_tool.py +438 -0
  37. quantalogic/tools/document_tools/markdown_to_html_tool.py +362 -0
  38. quantalogic/tools/document_tools/markdown_to_ipynb_tool.py +319 -0
  39. quantalogic/tools/document_tools/markdown_to_latex_tool.py +420 -0
  40. quantalogic/tools/document_tools/markdown_to_pdf_tool.py +623 -0
  41. quantalogic/tools/document_tools/markdown_to_pptx_tool.py +319 -0
  42. quantalogic/tools/duckduckgo_search_tool.py +2 -4
  43. quantalogic/tools/finance/alpha_vantage_tool.py +440 -0
  44. quantalogic/tools/finance/ccxt_tool.py +373 -0
  45. quantalogic/tools/finance/finance_llm_tool.py +387 -0
  46. quantalogic/tools/finance/google_finance.py +192 -0
  47. quantalogic/tools/finance/market_intelligence_tool.py +520 -0
  48. quantalogic/tools/finance/technical_analysis_tool.py +491 -0
  49. quantalogic/tools/finance/tradingview_tool.py +336 -0
  50. quantalogic/tools/finance/yahoo_finance.py +236 -0
  51. quantalogic/tools/git/bitbucket_clone_repo_tool.py +181 -0
  52. quantalogic/tools/git/bitbucket_operations_tool.py +326 -0
  53. quantalogic/tools/git/clone_repo_tool.py +189 -0
  54. quantalogic/tools/git/git_operations_tool.py +532 -0
  55. quantalogic/tools/google_packages/google_news_tool.py +480 -0
  56. quantalogic/tools/grep_app_tool.py +123 -186
  57. quantalogic/tools/{dalle_e.py → image_generation/dalle_e.py} +37 -27
  58. quantalogic/tools/jinja_tool.py +6 -10
  59. quantalogic/tools/language_handlers/__init__.py +22 -9
  60. quantalogic/tools/list_directory_tool.py +131 -42
  61. quantalogic/tools/llm_tool.py +45 -15
  62. quantalogic/tools/llm_vision_tool.py +59 -7
  63. quantalogic/tools/markitdown_tool.py +17 -5
  64. quantalogic/tools/nasa_packages/models.py +47 -0
  65. quantalogic/tools/nasa_packages/nasa_apod_tool.py +232 -0
  66. quantalogic/tools/nasa_packages/nasa_neows_tool.py +147 -0
  67. quantalogic/tools/nasa_packages/services.py +82 -0
  68. quantalogic/tools/presentation_tools/presentation_llm_tool.py +396 -0
  69. quantalogic/tools/product_hunt/product_hunt_tool.py +258 -0
  70. quantalogic/tools/product_hunt/services.py +63 -0
  71. quantalogic/tools/rag_tool/__init__.py +48 -0
  72. quantalogic/tools/rag_tool/document_metadata.py +15 -0
  73. quantalogic/tools/rag_tool/query_response.py +20 -0
  74. quantalogic/tools/rag_tool/rag_tool.py +566 -0
  75. quantalogic/tools/rag_tool/rag_tool_beta.py +264 -0
  76. quantalogic/tools/read_html_tool.py +24 -38
  77. quantalogic/tools/replace_in_file_tool.py +10 -10
  78. quantalogic/tools/safe_python_interpreter_tool.py +10 -24
  79. quantalogic/tools/search_definition_names.py +2 -2
  80. quantalogic/tools/sequence_tool.py +14 -23
  81. quantalogic/tools/sql_query_tool.py +17 -19
  82. quantalogic/tools/tool.py +39 -15
  83. quantalogic/tools/unified_diff_tool.py +1 -1
  84. quantalogic/tools/utilities/csv_processor_tool.py +234 -0
  85. quantalogic/tools/utilities/download_file_tool.py +179 -0
  86. quantalogic/tools/utilities/mermaid_validator_tool.py +661 -0
  87. quantalogic/tools/utils/__init__.py +1 -4
  88. quantalogic/tools/utils/create_sample_database.py +24 -38
  89. quantalogic/tools/utils/generate_database_report.py +74 -82
  90. quantalogic/tools/wikipedia_search_tool.py +17 -21
  91. quantalogic/utils/ask_user_validation.py +1 -1
  92. quantalogic/utils/async_utils.py +35 -0
  93. quantalogic/utils/check_version.py +3 -5
  94. quantalogic/utils/get_all_models.py +2 -1
  95. quantalogic/utils/git_ls.py +21 -7
  96. quantalogic/utils/lm_studio_model_info.py +9 -7
  97. quantalogic/utils/python_interpreter.py +113 -43
  98. quantalogic/utils/xml_utility.py +178 -0
  99. quantalogic/version_check.py +1 -1
  100. quantalogic/welcome_message.py +7 -7
  101. quantalogic/xml_parser.py +0 -1
  102. {quantalogic-0.35.0.dist-info → quantalogic-0.40.0.dist-info}/METADATA +41 -1
  103. quantalogic-0.40.0.dist-info/RECORD +148 -0
  104. quantalogic-0.35.0.dist-info/RECORD +0 -102
  105. {quantalogic-0.35.0.dist-info → quantalogic-0.40.0.dist-info}/LICENSE +0 -0
  106. {quantalogic-0.35.0.dist-info → quantalogic-0.40.0.dist-info}/WHEEL +0 -0
  107. {quantalogic-0.35.0.dist-info → quantalogic-0.40.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,387 @@
1
+ """Advanced Financial Analysis LLM Tool combining market data with AI insights."""
2
+
3
+ import json
4
+ from dataclasses import dataclass
5
+ from datetime import datetime
6
+ from typing import Any, Callable, ClassVar, Dict, List, Optional, Union
7
+
8
+ import pandas as pd
9
+ import ta
10
+ from loguru import logger
11
+ from pydantic import ConfigDict, Field
12
+
13
+ from quantalogic.agent import Agent
14
+ from quantalogic.event_emitter import EventEmitter
15
+ from quantalogic.generative_model import GenerativeModel, Message
16
+ from quantalogic.tools.finance.alpha_vantage_tool import AlphaVantageTool
17
+ from quantalogic.tools.finance.ccxt_tool import CCXTTool
18
+ from quantalogic.tools.tool import Tool, ToolArgument
19
+
20
+
21
+ @dataclass
22
+ class StrategyResult:
23
+ """Container for strategy analysis results."""
24
+ name: str
25
+ signals: Dict[str, str] # buy/sell/hold signals
26
+ confidence: float
27
+ reasoning: str
28
+ metrics: Dict[str, float]
29
+ timestamp: datetime
30
+
31
+ @dataclass
32
+ class MarketAnalysis:
33
+ """Container for comprehensive market analysis."""
34
+ symbol: str
35
+ asset_type: str
36
+ timeframe: str
37
+ current_price: float
38
+ analysis_timestamp: datetime
39
+ technical_signals: Dict[str, Dict[str, Union[str, float]]]
40
+ fundamental_data: Optional[Dict[str, Any]] = None
41
+ market_sentiment: Optional[Dict[str, float]] = None
42
+ strategy_results: List[StrategyResult] = None
43
+ ai_insights: Optional[Dict[str, Any]] = None
44
+
45
+ class FinanceLLMTool(Tool):
46
+ """Advanced financial analysis tool combining market data with AI insights."""
47
+
48
+ model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
49
+
50
+ name: str = Field(default="finance_llm_tool")
51
+ description: str = Field(
52
+ default=(
53
+ "Advanced financial analysis tool that combines real-time market data, "
54
+ "technical analysis, fundamental analysis, and AI-powered insights for "
55
+ "stocks, cryptocurrencies, and indices. Provides detailed strategy analysis "
56
+ "and market insights using state-of-the-art language models."
57
+ )
58
+ )
59
+
60
+ STRATEGIES: ClassVar[List[str]] = [
61
+ "trend_following",
62
+ "mean_reversion",
63
+ "breakout",
64
+ "momentum",
65
+ "volume_analysis",
66
+ "sentiment_based",
67
+ "multi_timeframe",
68
+ "adaptive_momentum",
69
+ "volatility_based",
70
+ "correlation_based"
71
+ ]
72
+
73
+ TIMEFRAMES: ClassVar[List[str]] = [
74
+ "1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", "1M"
75
+ ]
76
+
77
+ arguments: list = Field(
78
+ default=[
79
+ ToolArgument(
80
+ name="symbols",
81
+ arg_type="list",
82
+ description="List of trading symbols to analyze",
83
+ required=True,
84
+ example="['BTC/USDT', 'AAPL', 'SPY']"
85
+ ),
86
+ ToolArgument(
87
+ name="asset_types",
88
+ arg_type="list",
89
+ description="List of asset types (crypto/stock/index)",
90
+ required=True,
91
+ example="['crypto', 'stock', 'index']"
92
+ ),
93
+ ToolArgument(
94
+ name="strategies",
95
+ arg_type="list",
96
+ description="List of trading strategies to analyze",
97
+ required=False,
98
+ default="['trend_following', 'momentum', 'multi_timeframe']"
99
+ ),
100
+ ToolArgument(
101
+ name="timeframes",
102
+ arg_type="list",
103
+ description="List of timeframes to analyze",
104
+ required=False,
105
+ default="['1h', '4h', '1d']"
106
+ ),
107
+ ToolArgument(
108
+ name="analysis_types",
109
+ arg_type="list",
110
+ description="Types of analysis to perform (technical/fundamental/sentiment/ai)",
111
+ required=False,
112
+ default="['technical', 'sentiment', 'ai']"
113
+ ),
114
+ ToolArgument(
115
+ name="temperature",
116
+ arg_type="string",
117
+ description="LLM temperature for analysis generation",
118
+ required=False,
119
+ default="0.3"
120
+ )
121
+ ]
122
+ )
123
+
124
+ model_name: str = Field(..., description="The name of the language model to use")
125
+ system_prompt: str = Field(
126
+ default=(
127
+ "You are an expert financial analyst and trading strategist with deep knowledge of:"
128
+ "\n- Technical Analysis and Chart Patterns"
129
+ "\n- Fundamental Analysis and Valuation Methods"
130
+ "\n- Market Psychology and Sentiment Analysis"
131
+ "\n- Risk Management and Position Sizing"
132
+ "\n- Multi-Timeframe Analysis"
133
+ "\n- Inter-market Analysis and Correlations"
134
+ "\n- Machine Learning in Trading"
135
+ "\nYour role is to analyze market data and provide detailed, actionable insights while:"
136
+ "\n- Maintaining objectivity and avoiding bias"
137
+ "\n- Providing clear reasoning for all conclusions"
138
+ "\n- Highlighting potential risks and limitations"
139
+ "\n- Considering multiple timeframes and perspectives"
140
+ "\n- Adapting analysis to market conditions"
141
+ "\n- Following proper risk management principles"
142
+ )
143
+ )
144
+
145
+ def __init__(
146
+ self,
147
+ model_name: str,
148
+ system_prompt: str | None = None,
149
+ on_token: Callable | None = None,
150
+ name: str = "finance_llm_tool",
151
+ generative_model: GenerativeModel | None = None,
152
+ event_emitter: EventEmitter | None = None,
153
+ ):
154
+ """Initialize the Finance LLM tool."""
155
+ super().__init__(
156
+ **{
157
+ "model_name": model_name,
158
+ "system_prompt": system_prompt or self.system_prompt,
159
+ "on_token": on_token,
160
+ "name": name,
161
+ "generative_model": generative_model,
162
+ "event_emitter": event_emitter,
163
+ }
164
+ )
165
+
166
+ # Initialize market data tools
167
+ self.ccxt_tool = CCXTTool()
168
+ self.alpha_vantage_tool = AlphaVantageTool()
169
+
170
+ # Initialize the generative model
171
+ self.model_post_init(None)
172
+
173
+ async def _analyze_technical_indicators(self, df: pd.DataFrame) -> Dict[str, Dict[str, Union[str, float]]]:
174
+ """Analyze technical indicators and generate signals."""
175
+ signals = {}
176
+
177
+ try:
178
+ # Calculate indicators
179
+ df['sma_20'] = ta.trend.sma_indicator(df['close'], 20)
180
+ df['sma_50'] = ta.trend.sma_indicator(df['close'], 50)
181
+ df['sma_200'] = ta.trend.sma_indicator(df['close'], 200)
182
+ df['rsi'] = ta.momentum.rsi(df['close'])
183
+ df['macd'] = ta.trend.macd_diff(df['close'])
184
+ df['bbands_upper'] = ta.volatility.bollinger_hband(df['close'])
185
+ df['bbands_lower'] = ta.volatility.bollinger_lband(df['close'])
186
+
187
+ # Generate signals
188
+ signals['trend'] = {
189
+ 'signal': 'buy' if df['sma_20'].iloc[-1] > df['sma_50'].iloc[-1] else 'sell',
190
+ 'strength': abs(df['sma_20'].iloc[-1] - df['sma_50'].iloc[-1]) / df['close'].iloc[-1]
191
+ }
192
+
193
+ signals['momentum'] = {
194
+ 'signal': 'buy' if df['rsi'].iloc[-1] < 30 else 'sell' if df['rsi'].iloc[-1] > 70 else 'hold',
195
+ 'strength': abs(50 - df['rsi'].iloc[-1]) / 50
196
+ }
197
+
198
+ signals['macd'] = {
199
+ 'signal': 'buy' if df['macd'].iloc[-1] > 0 else 'sell',
200
+ 'strength': abs(df['macd'].iloc[-1])
201
+ }
202
+
203
+ return signals
204
+
205
+ except Exception as e:
206
+ logger.error(f"Error analyzing technical indicators: {e}")
207
+ raise
208
+
209
+ async def _analyze_strategy(
210
+ self,
211
+ df: pd.DataFrame,
212
+ strategy: str,
213
+ timeframe: str
214
+ ) -> StrategyResult:
215
+ """Analyze market data using a specific strategy."""
216
+ try:
217
+ # Prepare strategy prompt
218
+ strategy_prompt = self._get_strategy_prompt(strategy, df, timeframe)
219
+
220
+ # Get AI analysis
221
+ response = await self.generative_model.chat_completion(
222
+ messages=[
223
+ Message(role="system", content=self.system_prompt),
224
+ Message(role="user", content=strategy_prompt)
225
+ ],
226
+ temperature=0.3
227
+ )
228
+
229
+ # Parse response
230
+ analysis = json.loads(response.content)
231
+
232
+ return StrategyResult(
233
+ name=strategy,
234
+ signals=analysis['signals'],
235
+ confidence=analysis['confidence'],
236
+ reasoning=analysis['reasoning'],
237
+ metrics=analysis['metrics'],
238
+ timestamp=datetime.now()
239
+ )
240
+
241
+ except Exception as e:
242
+ logger.error(f"Error analyzing strategy {strategy}: {e}")
243
+ raise
244
+
245
+ def _get_strategy_prompt(self, strategy: str, df: pd.DataFrame, timeframe: str) -> str:
246
+ """Generate strategy-specific analysis prompt."""
247
+ base_prompt = (
248
+ f"Analyze the following market data using the {strategy} strategy on {timeframe} timeframe. "
249
+ "Provide analysis in JSON format with the following structure:\n"
250
+ "{\n"
251
+ ' "signals": {"primary": "buy/sell/hold", "secondary": "string"},\n'
252
+ ' "confidence": float between 0 and 1,\n'
253
+ ' "reasoning": "detailed explanation",\n'
254
+ ' "metrics": {"risk_reward": float, "probability": float}\n'
255
+ "}\n\n"
256
+ "Market Data Summary:\n"
257
+ )
258
+
259
+ # Add strategy-specific data points
260
+ if strategy == "trend_following":
261
+ base_prompt += (
262
+ f"Current Price: {df['close'].iloc[-1]}\n"
263
+ f"SMA20: {df['sma_20'].iloc[-1]}\n"
264
+ f"SMA50: {df['sma_50'].iloc[-1]}\n"
265
+ f"SMA200: {df['sma_200'].iloc[-1]}\n"
266
+ )
267
+ elif strategy == "momentum":
268
+ base_prompt += (
269
+ f"RSI: {df['rsi'].iloc[-1]}\n"
270
+ f"MACD: {df['macd'].iloc[-1]}\n"
271
+ f"Recent Price Change: {(df['close'].iloc[-1] / df['close'].iloc[-5] - 1) * 100}%\n"
272
+ )
273
+
274
+ return base_prompt
275
+
276
+ async def execute(self, agent: Agent, **kwargs) -> Dict[str, MarketAnalysis]:
277
+ """Execute comprehensive financial analysis."""
278
+ try:
279
+ symbols = kwargs['symbols']
280
+ asset_types = kwargs['asset_types']
281
+ strategies = kwargs.get('strategies', ['trend_following', 'momentum'])
282
+ timeframes = kwargs.get('timeframes', ['1h', '4h', '1d'])
283
+ analysis_types = kwargs.get('analysis_types', ['technical', 'sentiment', 'ai'])
284
+
285
+ results = {}
286
+
287
+ for symbol, asset_type in zip(symbols, asset_types):
288
+ # Fetch market data
289
+ if asset_type == 'crypto':
290
+ market_data = await self.ccxt_tool.execute(
291
+ agent=agent,
292
+ symbols=[symbol],
293
+ exchanges=['binance'],
294
+ timeframe='1h'
295
+ )
296
+ else:
297
+ market_data = await self.alpha_vantage_tool.execute(
298
+ agent=agent,
299
+ symbols=[symbol],
300
+ asset_types=[asset_type],
301
+ data_types=['daily', 'fundamental']
302
+ )
303
+
304
+ # Process each timeframe
305
+ for timeframe in timeframes:
306
+ df = pd.DataFrame(market_data[symbol]['market_data'])
307
+
308
+ # Technical Analysis
309
+ if 'technical' in analysis_types:
310
+ technical_signals = await self._analyze_technical_indicators(df)
311
+ else:
312
+ technical_signals = None
313
+
314
+ # Strategy Analysis
315
+ strategy_results = []
316
+ for strategy in strategies:
317
+ result = await self._analyze_strategy(df, strategy, timeframe)
318
+ strategy_results.append(result)
319
+
320
+ # AI Insights
321
+ if 'ai' in analysis_types:
322
+ ai_prompt = (
323
+ f"Analyze {symbol} on {timeframe} timeframe considering:\n"
324
+ f"1. Current market conditions\n"
325
+ f"2. Technical signals: {technical_signals}\n"
326
+ f"3. Strategy results: {strategy_results}\n"
327
+ "Provide insights in JSON format with:\n"
328
+ "- Overall market outlook\n"
329
+ "- Key levels to watch\n"
330
+ "- Risk factors\n"
331
+ "- Trading opportunities"
332
+ )
333
+
334
+ ai_response = await self.generative_model.chat_completion(
335
+ messages=[
336
+ Message(role="system", content=self.system_prompt),
337
+ Message(role="user", content=ai_prompt)
338
+ ],
339
+ temperature=float(kwargs.get('temperature', 0.3))
340
+ )
341
+
342
+ ai_insights = json.loads(ai_response.content)
343
+ else:
344
+ ai_insights = None
345
+
346
+ # Combine all analysis
347
+ results[f"{symbol}_{timeframe}"] = MarketAnalysis(
348
+ symbol=symbol,
349
+ asset_type=asset_type,
350
+ timeframe=timeframe,
351
+ current_price=df['close'].iloc[-1],
352
+ analysis_timestamp=datetime.now(),
353
+ technical_signals=technical_signals,
354
+ fundamental_data=market_data[symbol].get('fundamental_data'),
355
+ market_sentiment=market_data[symbol].get('news_sentiment'),
356
+ strategy_results=strategy_results,
357
+ ai_insights=ai_insights
358
+ )
359
+
360
+ return results
361
+
362
+ except Exception as e:
363
+ logger.error(f"Error executing financial analysis: {e}")
364
+ raise
365
+
366
+ def validate_arguments(self, **kwargs) -> bool:
367
+ """Validate the provided arguments."""
368
+ try:
369
+ required_args = [arg.name for arg in self.arguments if arg.required]
370
+ for arg in required_args:
371
+ if arg not in kwargs:
372
+ raise ValueError(f"Missing required argument: {arg}")
373
+
374
+ if 'strategies' in kwargs:
375
+ invalid_strategies = set(kwargs['strategies']) - set(self.STRATEGIES)
376
+ if invalid_strategies:
377
+ raise ValueError(f"Invalid strategies: {invalid_strategies}")
378
+
379
+ if 'timeframes' in kwargs:
380
+ invalid_timeframes = set(kwargs['timeframes']) - set(self.TIMEFRAMES)
381
+ if invalid_timeframes:
382
+ raise ValueError(f"Invalid timeframes: {invalid_timeframes}")
383
+
384
+ return True
385
+ except Exception as e:
386
+ logger.error(f"Argument validation error: {e}")
387
+ return False
@@ -0,0 +1,192 @@
1
+ from datetime import datetime
2
+ from typing import ClassVar, Dict
3
+
4
+ import pandas as pd
5
+ import requests
6
+ from loguru import logger
7
+
8
+ from quantalogic.tools import Tool, ToolArgument
9
+
10
+
11
+ class GFinanceTool(Tool):
12
+ """Enhanced Google Finance data retrieval and analysis tool."""
13
+
14
+ name: str = "gfinance_tool"
15
+ description: str = "Advanced financial data and analysis tool using Google Finance"
16
+ arguments: list[ToolArgument] = [
17
+ ToolArgument(name="ticker", arg_type="string", description="Stock symbol (e.g., GOOGL)", required=True),
18
+ ToolArgument(name="start_date", arg_type="string", description="Start date (YYYY-MM-DD)", required=True),
19
+ ToolArgument(name="end_date", arg_type="string", description="End date (YYYY-MM-DD)", required=True),
20
+ ToolArgument(
21
+ name="interval",
22
+ arg_type="string",
23
+ description="Data interval (1d/1wk/1mo)",
24
+ required=False,
25
+ default="1d"
26
+ ),
27
+ ToolArgument(
28
+ name="analysis_type",
29
+ arg_type="string",
30
+ description="Type of analysis to perform (technical/fundamental/all)",
31
+ required=False,
32
+ default="all"
33
+ )
34
+ ]
35
+
36
+ INTERVAL_MAPPING: ClassVar[Dict[str, int]] = {
37
+ '1d': 86400, # 1 day in seconds
38
+ '1wk': 604800, # 1 week in seconds
39
+ '1mo': 2592000 # 1 month in seconds (30 days)
40
+ }
41
+
42
+ BASE_URL = "https://www.google.com/finance/quote"
43
+
44
+ def __init__(self, **kwargs):
45
+ super().__init__(**kwargs)
46
+ self.cache = {}
47
+
48
+ def _validate_dates(self, start_date: str, end_date: str) -> tuple[datetime, datetime]:
49
+ """Validate and convert date strings to datetime objects."""
50
+ try:
51
+ start = datetime.strptime(start_date, "%Y-%m-%d")
52
+ end = datetime.strptime(end_date, "%Y-%m-%d")
53
+ if start > end:
54
+ raise ValueError("Start date must be before end date")
55
+ return start, end
56
+ except ValueError as e:
57
+ logger.error(f"Date validation error: {e}")
58
+ raise
59
+
60
+ def _fetch_data(self, ticker: str, start_date: datetime, end_date: datetime, interval: str) -> pd.DataFrame:
61
+ """Fetch financial data from Google Finance."""
62
+ try:
63
+ # Construct the URL
64
+ params = {
65
+ 'period': self.INTERVAL_MAPPING.get(interval, self.INTERVAL_MAPPING['1d']),
66
+ 'window': int((end_date - start_date).total_seconds())
67
+ }
68
+ url = f"{self.BASE_URL}/{ticker}/historical"
69
+
70
+ # Make the request
71
+ response = requests.get(url, params=params)
72
+ response.raise_for_status()
73
+
74
+ # Parse the response and convert to DataFrame
75
+ data = response.json()
76
+ df = pd.DataFrame(data['prices'])
77
+ df['timestamp'] = pd.to_datetime(df['timestamp'], unit='s')
78
+ df.set_index('timestamp', inplace=True)
79
+
80
+ return df
81
+
82
+ except requests.RequestException as e:
83
+ logger.error(f"Error fetching data from Google Finance: {e}")
84
+ raise
85
+
86
+ def _calculate_technical_indicators(self, df: pd.DataFrame) -> pd.DataFrame:
87
+ """Calculate technical indicators."""
88
+ if len(df) < 50:
89
+ logger.warning("Not enough data points for technical analysis")
90
+ return df
91
+
92
+ # Moving Averages
93
+ df['SMA_20'] = df['close'].rolling(window=20).mean()
94
+ df['SMA_50'] = df['close'].rolling(window=50).mean()
95
+ df['EMA_12'] = df['close'].ewm(span=12, adjust=False).mean()
96
+ df['EMA_26'] = df['close'].ewm(span=26, adjust=False).mean()
97
+
98
+ # MACD
99
+ df['MACD'] = df['EMA_12'] - df['EMA_26']
100
+ df['MACD_Signal'] = df['MACD'].ewm(span=9, adjust=False).mean()
101
+
102
+ # RSI
103
+ delta = df['close'].diff()
104
+ gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
105
+ loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
106
+ rs = gain / loss
107
+ df['RSI'] = 100 - (100 / (1 + rs))
108
+
109
+ # Bollinger Bands
110
+ df['BB_middle'] = df['close'].rolling(window=20).mean()
111
+ std = df['close'].rolling(window=20).std()
112
+ df['BB_upper'] = df['BB_middle'] + (std * 2)
113
+ df['BB_lower'] = df['BB_middle'] - (std * 2)
114
+
115
+ return df
116
+
117
+ def _get_fundamental_data(self, ticker: str) -> Dict:
118
+ """Fetch fundamental data from Google Finance."""
119
+ try:
120
+ url = f"{self.BASE_URL}/{ticker}"
121
+ response = requests.get(url)
122
+ response.raise_for_status()
123
+
124
+ # Parse the response to extract fundamental data
125
+ data = response.json()
126
+ return {
127
+ 'market_cap': data.get('marketCap'),
128
+ 'pe_ratio': data.get('peRatio'),
129
+ 'dividend_yield': data.get('dividendYield'),
130
+ 'eps': data.get('eps'),
131
+ 'high_52week': data.get('high52Week'),
132
+ 'low_52week': data.get('low52Week')
133
+ }
134
+
135
+ except requests.RequestException as e:
136
+ logger.error(f"Error fetching fundamental data: {e}")
137
+ return {}
138
+
139
+ def execute(self, **kwargs) -> Dict:
140
+ """Execute the Google Finance tool with the provided parameters."""
141
+ ticker = kwargs.get('ticker')
142
+ start_date = kwargs.get('start_date')
143
+ end_date = kwargs.get('end_date')
144
+ interval = kwargs.get('interval', '1d')
145
+ analysis_type = kwargs.get('analysis_type', 'all')
146
+
147
+ try:
148
+ # Validate dates
149
+ start_dt, end_dt = self._validate_dates(start_date, end_date)
150
+
151
+ # Fetch historical data
152
+ df = self._fetch_data(ticker, start_dt, end_dt, interval)
153
+
154
+ result = {
155
+ 'historical_data': df.to_dict(orient='records'),
156
+ 'metadata': {
157
+ 'ticker': ticker,
158
+ 'start_date': start_date,
159
+ 'end_date': end_date,
160
+ 'interval': interval
161
+ }
162
+ }
163
+
164
+ # Add technical analysis if requested
165
+ if analysis_type in ['technical', 'all']:
166
+ df = self._calculate_technical_indicators(df)
167
+ result['technical_indicators'] = df.to_dict(orient='records')
168
+
169
+ # Add fundamental analysis if requested
170
+ if analysis_type in ['fundamental', 'all']:
171
+ fundamental_data = self._get_fundamental_data(ticker)
172
+ result['fundamental_data'] = fundamental_data
173
+
174
+ return result
175
+
176
+ except Exception as e:
177
+ logger.error(f"Error executing Google Finance tool: {e}")
178
+ raise
179
+
180
+ def validate_arguments(self, **kwargs) -> bool:
181
+ """Validate the provided arguments."""
182
+ required_args = [arg.name for arg in self.arguments if arg.required]
183
+ for arg in required_args:
184
+ if arg not in kwargs:
185
+ logger.error(f"Missing required argument: {arg}")
186
+ return False
187
+
188
+ if 'interval' in kwargs and kwargs['interval'] not in self.INTERVAL_MAPPING:
189
+ logger.error(f"Invalid interval: {kwargs['interval']}")
190
+ return False
191
+
192
+ return True