quantalogic 0.33.4__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.
- quantalogic/__init__.py +0 -4
- quantalogic/agent.py +603 -362
- quantalogic/agent_config.py +260 -28
- quantalogic/agent_factory.py +43 -17
- quantalogic/coding_agent.py +20 -12
- quantalogic/config.py +7 -4
- quantalogic/console_print_events.py +4 -8
- quantalogic/console_print_token.py +2 -2
- quantalogic/docs_cli.py +15 -10
- quantalogic/event_emitter.py +258 -83
- quantalogic/flow/__init__.py +23 -0
- quantalogic/flow/flow.py +595 -0
- quantalogic/flow/flow_extractor.py +672 -0
- quantalogic/flow/flow_generator.py +89 -0
- quantalogic/flow/flow_manager.py +407 -0
- quantalogic/flow/flow_manager_schema.py +169 -0
- quantalogic/flow/flow_yaml.md +419 -0
- quantalogic/generative_model.py +109 -77
- quantalogic/get_model_info.py +6 -6
- quantalogic/interactive_text_editor.py +100 -73
- quantalogic/main.py +36 -23
- quantalogic/model_info_list.py +12 -0
- quantalogic/model_info_litellm.py +14 -14
- quantalogic/prompts.py +2 -1
- quantalogic/{llm.py → quantlitellm.py} +29 -39
- quantalogic/search_agent.py +4 -4
- quantalogic/server/models.py +4 -1
- quantalogic/task_file_reader.py +5 -5
- quantalogic/task_runner.py +21 -20
- quantalogic/tool_manager.py +10 -21
- quantalogic/tools/__init__.py +98 -68
- quantalogic/tools/composio/composio.py +416 -0
- quantalogic/tools/{generate_database_report_tool.py → database/generate_database_report_tool.py} +4 -9
- quantalogic/tools/database/sql_query_tool_advanced.py +261 -0
- quantalogic/tools/document_tools/markdown_to_docx_tool.py +620 -0
- quantalogic/tools/document_tools/markdown_to_epub_tool.py +438 -0
- quantalogic/tools/document_tools/markdown_to_html_tool.py +362 -0
- quantalogic/tools/document_tools/markdown_to_ipynb_tool.py +319 -0
- quantalogic/tools/document_tools/markdown_to_latex_tool.py +420 -0
- quantalogic/tools/document_tools/markdown_to_pdf_tool.py +623 -0
- quantalogic/tools/document_tools/markdown_to_pptx_tool.py +319 -0
- quantalogic/tools/duckduckgo_search_tool.py +2 -4
- quantalogic/tools/finance/alpha_vantage_tool.py +440 -0
- quantalogic/tools/finance/ccxt_tool.py +373 -0
- quantalogic/tools/finance/finance_llm_tool.py +387 -0
- quantalogic/tools/finance/google_finance.py +192 -0
- quantalogic/tools/finance/market_intelligence_tool.py +520 -0
- quantalogic/tools/finance/technical_analysis_tool.py +491 -0
- quantalogic/tools/finance/tradingview_tool.py +336 -0
- quantalogic/tools/finance/yahoo_finance.py +236 -0
- quantalogic/tools/git/bitbucket_clone_repo_tool.py +181 -0
- quantalogic/tools/git/bitbucket_operations_tool.py +326 -0
- quantalogic/tools/git/clone_repo_tool.py +189 -0
- quantalogic/tools/git/git_operations_tool.py +532 -0
- quantalogic/tools/google_packages/google_news_tool.py +480 -0
- quantalogic/tools/grep_app_tool.py +123 -186
- quantalogic/tools/{dalle_e.py → image_generation/dalle_e.py} +37 -27
- quantalogic/tools/jinja_tool.py +6 -10
- quantalogic/tools/language_handlers/__init__.py +22 -9
- quantalogic/tools/list_directory_tool.py +131 -42
- quantalogic/tools/llm_tool.py +45 -15
- quantalogic/tools/llm_vision_tool.py +59 -7
- quantalogic/tools/markitdown_tool.py +17 -5
- quantalogic/tools/nasa_packages/models.py +47 -0
- quantalogic/tools/nasa_packages/nasa_apod_tool.py +232 -0
- quantalogic/tools/nasa_packages/nasa_neows_tool.py +147 -0
- quantalogic/tools/nasa_packages/services.py +82 -0
- quantalogic/tools/presentation_tools/presentation_llm_tool.py +396 -0
- quantalogic/tools/product_hunt/product_hunt_tool.py +258 -0
- quantalogic/tools/product_hunt/services.py +63 -0
- quantalogic/tools/rag_tool/__init__.py +48 -0
- quantalogic/tools/rag_tool/document_metadata.py +15 -0
- quantalogic/tools/rag_tool/query_response.py +20 -0
- quantalogic/tools/rag_tool/rag_tool.py +566 -0
- quantalogic/tools/rag_tool/rag_tool_beta.py +264 -0
- quantalogic/tools/read_html_tool.py +24 -38
- quantalogic/tools/replace_in_file_tool.py +10 -10
- quantalogic/tools/safe_python_interpreter_tool.py +10 -24
- quantalogic/tools/search_definition_names.py +2 -2
- quantalogic/tools/sequence_tool.py +14 -23
- quantalogic/tools/sql_query_tool.py +17 -19
- quantalogic/tools/tool.py +39 -15
- quantalogic/tools/unified_diff_tool.py +1 -1
- quantalogic/tools/utilities/csv_processor_tool.py +234 -0
- quantalogic/tools/utilities/download_file_tool.py +179 -0
- quantalogic/tools/utilities/mermaid_validator_tool.py +661 -0
- quantalogic/tools/utils/__init__.py +1 -4
- quantalogic/tools/utils/create_sample_database.py +24 -38
- quantalogic/tools/utils/generate_database_report.py +74 -82
- quantalogic/tools/wikipedia_search_tool.py +17 -21
- quantalogic/utils/ask_user_validation.py +1 -1
- quantalogic/utils/async_utils.py +35 -0
- quantalogic/utils/check_version.py +3 -5
- quantalogic/utils/get_all_models.py +2 -1
- quantalogic/utils/git_ls.py +21 -7
- quantalogic/utils/lm_studio_model_info.py +9 -7
- quantalogic/utils/python_interpreter.py +113 -43
- quantalogic/utils/xml_utility.py +178 -0
- quantalogic/version_check.py +1 -1
- quantalogic/welcome_message.py +7 -7
- quantalogic/xml_parser.py +0 -1
- {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/METADATA +44 -1
- quantalogic-0.40.0.dist-info/RECORD +148 -0
- quantalogic-0.33.4.dist-info/RECORD +0 -102
- {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/LICENSE +0 -0
- {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/WHEEL +0 -0
- {quantalogic-0.33.4.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
|