quantalogic 0.35.0__py3-none-any.whl → 0.50.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 -363
- quantalogic/agent_config.py +233 -46
- quantalogic/agent_factory.py +34 -22
- quantalogic/coding_agent.py +16 -14
- quantalogic/config.py +2 -1
- 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 +5 -5
- quantalogic/interactive_text_editor.py +100 -73
- quantalogic/main.py +17 -21
- quantalogic/model_info_list.py +3 -3
- 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 +20 -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.35.0.dist-info → quantalogic-0.50.0.dist-info}/METADATA +40 -1
- quantalogic-0.50.0.dist-info/RECORD +148 -0
- quantalogic-0.35.0.dist-info/RECORD +0 -102
- {quantalogic-0.35.0.dist-info → quantalogic-0.50.0.dist-info}/LICENSE +0 -0
- {quantalogic-0.35.0.dist-info → quantalogic-0.50.0.dist-info}/WHEEL +0 -0
- {quantalogic-0.35.0.dist-info → quantalogic-0.50.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,520 @@
|
|
1
|
+
"""Advanced Market Intelligence Tool for comprehensive financial information retrieval."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from datetime import datetime
|
6
|
+
from typing import Any, Dict, List, Optional
|
7
|
+
|
8
|
+
import ccxt
|
9
|
+
import numpy as np
|
10
|
+
import pandas as pd
|
11
|
+
import requests
|
12
|
+
import tweepy
|
13
|
+
import yfinance as yf
|
14
|
+
from alpha_vantage.fundamentaldata import FundamentalData
|
15
|
+
from alpha_vantage.timeseries import TimeSeries
|
16
|
+
from loguru import logger
|
17
|
+
from newsapi import NewsApiClient
|
18
|
+
from serpapi import GoogleSearch
|
19
|
+
|
20
|
+
from quantalogic.agent import Agent
|
21
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
22
|
+
|
23
|
+
|
24
|
+
@dataclass
|
25
|
+
class MarketNews:
|
26
|
+
"""Container for market news data."""
|
27
|
+
title: str
|
28
|
+
source: str
|
29
|
+
url: str
|
30
|
+
published_at: datetime
|
31
|
+
sentiment: float
|
32
|
+
relevance: float
|
33
|
+
summary: str
|
34
|
+
keywords: List[str]
|
35
|
+
|
36
|
+
@dataclass
|
37
|
+
class SocialMetrics:
|
38
|
+
"""Container for social media metrics."""
|
39
|
+
sentiment_score: float
|
40
|
+
engagement_rate: float
|
41
|
+
mention_count: int
|
42
|
+
trending_topics: List[str]
|
43
|
+
influential_posts: List[Dict]
|
44
|
+
source: str
|
45
|
+
timestamp: datetime
|
46
|
+
|
47
|
+
@dataclass
|
48
|
+
class FundamentalMetrics:
|
49
|
+
"""Container for fundamental analysis metrics."""
|
50
|
+
market_cap: float
|
51
|
+
pe_ratio: Optional[float]
|
52
|
+
pb_ratio: Optional[float]
|
53
|
+
debt_to_equity: Optional[float]
|
54
|
+
current_ratio: Optional[float]
|
55
|
+
quick_ratio: Optional[float]
|
56
|
+
roa: Optional[float]
|
57
|
+
roe: Optional[float]
|
58
|
+
profit_margin: Optional[float]
|
59
|
+
revenue_growth: Optional[float]
|
60
|
+
institutional_ownership: Optional[float]
|
61
|
+
insider_ownership: Optional[float]
|
62
|
+
|
63
|
+
@dataclass
|
64
|
+
class MarketIntelligence:
|
65
|
+
"""Container for comprehensive market intelligence."""
|
66
|
+
symbol: str
|
67
|
+
asset_type: str
|
68
|
+
timestamp: datetime
|
69
|
+
current_price: float
|
70
|
+
market_news: List[MarketNews]
|
71
|
+
social_metrics: Dict[str, SocialMetrics]
|
72
|
+
fundamental_metrics: Optional[FundamentalMetrics]
|
73
|
+
analyst_ratings: Dict[str, Any]
|
74
|
+
market_events: List[Dict]
|
75
|
+
regulatory_updates: List[Dict]
|
76
|
+
competitor_analysis: Dict[str, Any]
|
77
|
+
risk_metrics: Dict[str, float]
|
78
|
+
|
79
|
+
class MarketIntelligenceTool(Tool):
|
80
|
+
"""Advanced market intelligence tool for comprehensive financial information retrieval."""
|
81
|
+
|
82
|
+
name: str = "market_intelligence_tool"
|
83
|
+
description: str = (
|
84
|
+
"Advanced tool for retrieving comprehensive market intelligence including "
|
85
|
+
"news, social metrics, fundamental data, and market events for various "
|
86
|
+
"financial instruments."
|
87
|
+
)
|
88
|
+
|
89
|
+
arguments: list = [
|
90
|
+
ToolArgument(
|
91
|
+
name="symbols",
|
92
|
+
arg_type="list",
|
93
|
+
description="List of trading symbols to analyze",
|
94
|
+
required=True,
|
95
|
+
example="['BTC/USDT', 'AAPL', 'SPY']"
|
96
|
+
),
|
97
|
+
ToolArgument(
|
98
|
+
name="asset_types",
|
99
|
+
arg_type="list",
|
100
|
+
description="List of asset types (crypto/stock/index)",
|
101
|
+
required=True,
|
102
|
+
example="['crypto', 'stock', 'index']"
|
103
|
+
),
|
104
|
+
ToolArgument(
|
105
|
+
name="data_types",
|
106
|
+
arg_type="list",
|
107
|
+
description="Types of data to retrieve",
|
108
|
+
required=False,
|
109
|
+
default="['all']"
|
110
|
+
),
|
111
|
+
ToolArgument(
|
112
|
+
name="time_range",
|
113
|
+
arg_type="string",
|
114
|
+
description="Time range for historical data",
|
115
|
+
required=False,
|
116
|
+
default="7d"
|
117
|
+
)
|
118
|
+
]
|
119
|
+
|
120
|
+
def __init__(self, **kwargs):
|
121
|
+
"""Initialize the Market Intelligence tool with API configurations."""
|
122
|
+
super().__init__(**kwargs)
|
123
|
+
|
124
|
+
# Initialize API clients
|
125
|
+
self._init_api_clients()
|
126
|
+
|
127
|
+
# Initialize cache
|
128
|
+
self.cache = {}
|
129
|
+
|
130
|
+
def _init_api_clients(self):
|
131
|
+
"""Initialize various API clients."""
|
132
|
+
try:
|
133
|
+
# News API
|
134
|
+
self.news_api = NewsApiClient(api_key=os.getenv('NEWS_API_KEY'))
|
135
|
+
|
136
|
+
# Alpha Vantage
|
137
|
+
self.alpha_vantage_fd = FundamentalData(key=os.getenv('ALPHA_VANTAGE_KEY'))
|
138
|
+
self.alpha_vantage_ts = TimeSeries(key=os.getenv('ALPHA_VANTAGE_KEY'))
|
139
|
+
|
140
|
+
# Twitter API
|
141
|
+
auth = tweepy.OAuthHandler(
|
142
|
+
os.getenv('TWITTER_API_KEY'),
|
143
|
+
os.getenv('TWITTER_API_SECRET')
|
144
|
+
)
|
145
|
+
auth.set_access_token(
|
146
|
+
os.getenv('TWITTER_ACCESS_TOKEN'),
|
147
|
+
os.getenv('TWITTER_ACCESS_TOKEN_SECRET')
|
148
|
+
)
|
149
|
+
self.twitter_api = tweepy.API(auth)
|
150
|
+
|
151
|
+
# SerpAPI
|
152
|
+
self.serp_api = GoogleSearch({
|
153
|
+
"api_key": os.getenv('SERPAPI_KEY')
|
154
|
+
})
|
155
|
+
|
156
|
+
# CCXT
|
157
|
+
self.exchange = ccxt.binance()
|
158
|
+
|
159
|
+
except Exception as e:
|
160
|
+
logger.error(f"Error initializing API clients: {e}")
|
161
|
+
raise
|
162
|
+
|
163
|
+
async def _fetch_market_news(self, symbol: str, asset_type: str) -> List[MarketNews]:
|
164
|
+
"""Fetch and analyze market news from multiple sources."""
|
165
|
+
news_items = []
|
166
|
+
|
167
|
+
try:
|
168
|
+
# News API
|
169
|
+
news_query = f"{symbol} stock" if asset_type == "stock" else f"{symbol} crypto"
|
170
|
+
news_response = self.news_api.get_everything(
|
171
|
+
q=news_query,
|
172
|
+
language='en',
|
173
|
+
sort_by='relevancy',
|
174
|
+
page_size=20
|
175
|
+
)
|
176
|
+
|
177
|
+
# Google News via SerpAPI
|
178
|
+
serp_params = {
|
179
|
+
"q": f"{symbol} news",
|
180
|
+
"tbm": "nws",
|
181
|
+
"num": 20
|
182
|
+
}
|
183
|
+
serp_response = self.serp_api.get_dict()
|
184
|
+
|
185
|
+
# Combine and process news
|
186
|
+
for article in news_response['articles'] + serp_response.get('news_results', []):
|
187
|
+
# Analyze sentiment
|
188
|
+
sentiment = self._analyze_sentiment(article['title'] + " " + article.get('description', ''))
|
189
|
+
|
190
|
+
# Calculate relevance
|
191
|
+
relevance = self._calculate_relevance(article, symbol)
|
192
|
+
|
193
|
+
news_items.append(MarketNews(
|
194
|
+
title=article['title'],
|
195
|
+
source=article['source']['name'],
|
196
|
+
url=article['url'],
|
197
|
+
published_at=datetime.strptime(article['publishedAt'][:19], '%Y-%m-%dT%H:%M:%S'),
|
198
|
+
sentiment=sentiment,
|
199
|
+
relevance=relevance,
|
200
|
+
summary=article.get('description', ''),
|
201
|
+
keywords=self._extract_keywords(article)
|
202
|
+
))
|
203
|
+
|
204
|
+
return sorted(news_items, key=lambda x: (x.relevance, x.published_at), reverse=True)
|
205
|
+
|
206
|
+
except Exception as e:
|
207
|
+
logger.error(f"Error fetching market news: {e}")
|
208
|
+
return []
|
209
|
+
|
210
|
+
async def _fetch_social_metrics(self, symbol: str, asset_type: str) -> Dict[str, SocialMetrics]:
|
211
|
+
"""Fetch and analyze social media metrics."""
|
212
|
+
social_data = {}
|
213
|
+
|
214
|
+
try:
|
215
|
+
# Twitter Analysis
|
216
|
+
twitter_metrics = await self._analyze_twitter(symbol)
|
217
|
+
social_data['twitter'] = twitter_metrics
|
218
|
+
|
219
|
+
# Reddit Analysis
|
220
|
+
reddit_metrics = await self._analyze_reddit(symbol)
|
221
|
+
social_data['reddit'] = reddit_metrics
|
222
|
+
|
223
|
+
# StockTwits Analysis (if stock)
|
224
|
+
if asset_type == 'stock':
|
225
|
+
stocktwits_metrics = await self._analyze_stocktwits(symbol)
|
226
|
+
social_data['stocktwits'] = stocktwits_metrics
|
227
|
+
|
228
|
+
return social_data
|
229
|
+
|
230
|
+
except Exception as e:
|
231
|
+
logger.error(f"Error fetching social metrics: {e}")
|
232
|
+
return {}
|
233
|
+
|
234
|
+
async def _fetch_fundamental_data(self, symbol: str, asset_type: str) -> Optional[FundamentalMetrics]:
|
235
|
+
"""Fetch fundamental data for stocks."""
|
236
|
+
if asset_type != 'stock':
|
237
|
+
return None
|
238
|
+
|
239
|
+
try:
|
240
|
+
# Alpha Vantage Fundamental Data
|
241
|
+
overview = self.alpha_vantage_fd.get_company_overview(symbol)[0]
|
242
|
+
|
243
|
+
# Yahoo Finance Additional Data
|
244
|
+
yf_data = yf.Ticker(symbol)
|
245
|
+
info = yf_data.info
|
246
|
+
|
247
|
+
return FundamentalMetrics(
|
248
|
+
market_cap=float(overview.get('MarketCapitalization', 0)),
|
249
|
+
pe_ratio=float(overview.get('PERatio', 0)) or None,
|
250
|
+
pb_ratio=float(overview.get('PriceToBookRatio', 0)) or None,
|
251
|
+
debt_to_equity=float(overview.get('DebtToEquityRatio', 0)) or None,
|
252
|
+
current_ratio=float(overview.get('CurrentRatio', 0)) or None,
|
253
|
+
quick_ratio=float(info.get('quickRatio', 0)) or None,
|
254
|
+
roa=float(overview.get('ReturnOnAssetsTTM', 0)) or None,
|
255
|
+
roe=float(overview.get('ReturnOnEquityTTM', 0)) or None,
|
256
|
+
profit_margin=float(overview.get('ProfitMargin', 0)) or None,
|
257
|
+
revenue_growth=float(info.get('revenueGrowth', 0)) or None,
|
258
|
+
institutional_ownership=float(info.get('institutionPercentHeld', 0)) or None,
|
259
|
+
insider_ownership=float(info.get('insiderPercentHeld', 0)) or None
|
260
|
+
)
|
261
|
+
|
262
|
+
except Exception as e:
|
263
|
+
logger.error(f"Error fetching fundamental data: {e}")
|
264
|
+
return None
|
265
|
+
|
266
|
+
async def _fetch_analyst_ratings(self, symbol: str, asset_type: str) -> Dict[str, Any]:
|
267
|
+
"""Fetch and analyze analyst ratings and price targets."""
|
268
|
+
ratings = {}
|
269
|
+
|
270
|
+
try:
|
271
|
+
if asset_type == 'stock':
|
272
|
+
# Yahoo Finance Analyst Ratings
|
273
|
+
yf_data = yf.Ticker(symbol)
|
274
|
+
recommendations = yf_data.recommendations
|
275
|
+
|
276
|
+
if recommendations is not None:
|
277
|
+
ratings['recommendations'] = recommendations.to_dict('records')
|
278
|
+
|
279
|
+
# Seeking Alpha via SerpAPI
|
280
|
+
serp_params = {
|
281
|
+
"q": f"{symbol} stock analysis site:seekingalpha.com",
|
282
|
+
"num": 5
|
283
|
+
}
|
284
|
+
serp_response = self.serp_api.get_dict()
|
285
|
+
|
286
|
+
if 'organic_results' in serp_response:
|
287
|
+
ratings['analysis_articles'] = serp_response['organic_results']
|
288
|
+
|
289
|
+
elif asset_type == 'crypto':
|
290
|
+
# CoinGecko API for crypto
|
291
|
+
url = f"https://api.coingecko.com/api/v3/coins/{symbol.lower()}/market_data"
|
292
|
+
response = requests.get(url)
|
293
|
+
if response.status_code == 200:
|
294
|
+
ratings['market_data'] = response.json()
|
295
|
+
|
296
|
+
return ratings
|
297
|
+
|
298
|
+
except Exception as e:
|
299
|
+
logger.error(f"Error fetching analyst ratings: {e}")
|
300
|
+
return {}
|
301
|
+
|
302
|
+
async def _fetch_market_events(self, symbol: str, asset_type: str) -> List[Dict]:
|
303
|
+
"""Fetch significant market events and calendar items."""
|
304
|
+
events = []
|
305
|
+
|
306
|
+
try:
|
307
|
+
if asset_type == 'stock':
|
308
|
+
# Earnings Calendar
|
309
|
+
yf_data = yf.Ticker(symbol)
|
310
|
+
calendar = yf_data.calendar
|
311
|
+
if calendar is not None:
|
312
|
+
events.extend([
|
313
|
+
{'type': 'earnings', 'data': calendar}
|
314
|
+
])
|
315
|
+
|
316
|
+
# SEC Filings
|
317
|
+
filings = yf_data.get_sec_filings()
|
318
|
+
if filings is not None:
|
319
|
+
events.extend([
|
320
|
+
{'type': 'sec_filing', 'data': filing}
|
321
|
+
for filing in filings.to_dict('records')
|
322
|
+
])
|
323
|
+
|
324
|
+
elif asset_type == 'crypto':
|
325
|
+
# CoinGecko Events
|
326
|
+
url = f"https://api.coingecko.com/api/v3/coins/{symbol.lower()}/events"
|
327
|
+
response = requests.get(url)
|
328
|
+
if response.status_code == 200:
|
329
|
+
events.extend([
|
330
|
+
{'type': 'crypto_event', 'data': event}
|
331
|
+
for event in response.json()['data']
|
332
|
+
])
|
333
|
+
|
334
|
+
return events
|
335
|
+
|
336
|
+
except Exception as e:
|
337
|
+
logger.error(f"Error fetching market events: {e}")
|
338
|
+
return []
|
339
|
+
|
340
|
+
async def _fetch_regulatory_updates(self, symbol: str, asset_type: str) -> List[Dict]:
|
341
|
+
"""Fetch regulatory updates and compliance information."""
|
342
|
+
updates = []
|
343
|
+
|
344
|
+
try:
|
345
|
+
# SEC API for stocks
|
346
|
+
if asset_type == 'stock':
|
347
|
+
sec_url = f"https://data.sec.gov/submissions/CIK{symbol}.json"
|
348
|
+
response = requests.get(sec_url)
|
349
|
+
if response.status_code == 200:
|
350
|
+
updates.extend([
|
351
|
+
{'type': 'sec_update', 'data': filing}
|
352
|
+
for filing in response.json()['filings']['recent']
|
353
|
+
])
|
354
|
+
|
355
|
+
# Regulatory news search
|
356
|
+
serp_params = {
|
357
|
+
"q": f"{symbol} regulation compliance news",
|
358
|
+
"tbm": "nws",
|
359
|
+
"num": 5
|
360
|
+
}
|
361
|
+
serp_response = self.serp_api.get_dict()
|
362
|
+
|
363
|
+
if 'news_results' in serp_response:
|
364
|
+
updates.extend([
|
365
|
+
{'type': 'regulatory_news', 'data': news}
|
366
|
+
for news in serp_response['news_results']
|
367
|
+
])
|
368
|
+
|
369
|
+
return updates
|
370
|
+
|
371
|
+
except Exception as e:
|
372
|
+
logger.error(f"Error fetching regulatory updates: {e}")
|
373
|
+
return []
|
374
|
+
|
375
|
+
async def _analyze_competitors(self, symbol: str, asset_type: str) -> Dict[str, Any]:
|
376
|
+
"""Analyze competitor performance and relative metrics."""
|
377
|
+
analysis = {}
|
378
|
+
|
379
|
+
try:
|
380
|
+
if asset_type == 'stock':
|
381
|
+
# Get sector peers
|
382
|
+
yf_data = yf.Ticker(symbol)
|
383
|
+
info = yf_data.info
|
384
|
+
|
385
|
+
if 'sector' in info:
|
386
|
+
# Find companies in same sector
|
387
|
+
sector_etf = self._get_sector_etf(info['sector'])
|
388
|
+
if sector_etf:
|
389
|
+
etf_data = yf.Ticker(sector_etf)
|
390
|
+
holdings = etf_data.get_holdings()
|
391
|
+
|
392
|
+
if holdings is not None:
|
393
|
+
analysis['sector_peers'] = holdings.head(10).to_dict('records')
|
394
|
+
|
395
|
+
# Relative performance
|
396
|
+
if 'sector_peers' in analysis:
|
397
|
+
peer_performance = {}
|
398
|
+
for peer in analysis['sector_peers']:
|
399
|
+
peer_data = yf.Ticker(peer['symbol'])
|
400
|
+
peer_performance[peer['symbol']] = {
|
401
|
+
'return_1y': peer_data.info.get('regularMarketPrice', 0) /
|
402
|
+
peer_data.info.get('regularMarketPreviousClose', 1) - 1,
|
403
|
+
'market_cap': peer_data.info.get('marketCap', 0),
|
404
|
+
'pe_ratio': peer_data.info.get('forwardPE', 0)
|
405
|
+
}
|
406
|
+
analysis['peer_performance'] = peer_performance
|
407
|
+
|
408
|
+
elif asset_type == 'crypto':
|
409
|
+
# Get top coins by market cap
|
410
|
+
url = "https://api.coingecko.com/api/v3/coins/markets"
|
411
|
+
params = {
|
412
|
+
'vs_currency': 'usd',
|
413
|
+
'order': 'market_cap_desc',
|
414
|
+
'per_page': 10,
|
415
|
+
'page': 1
|
416
|
+
}
|
417
|
+
response = requests.get(url, params=params)
|
418
|
+
if response.status_code == 200:
|
419
|
+
analysis['market_leaders'] = response.json()
|
420
|
+
|
421
|
+
return analysis
|
422
|
+
|
423
|
+
except Exception as e:
|
424
|
+
logger.error(f"Error analyzing competitors: {e}")
|
425
|
+
return {}
|
426
|
+
|
427
|
+
async def _calculate_risk_metrics(self, symbol: str, asset_type: str) -> Dict[str, float]:
|
428
|
+
"""Calculate comprehensive risk metrics."""
|
429
|
+
risk_metrics = {}
|
430
|
+
|
431
|
+
try:
|
432
|
+
# Get historical data
|
433
|
+
if asset_type == 'stock':
|
434
|
+
data = yf.download(symbol, period='1y')
|
435
|
+
else:
|
436
|
+
data = self.exchange.fetch_ohlcv(symbol, '1d', limit=365)
|
437
|
+
data = pd.DataFrame(data, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
|
438
|
+
|
439
|
+
# Calculate risk metrics
|
440
|
+
returns = data['close'].pct_change().dropna()
|
441
|
+
|
442
|
+
risk_metrics.update({
|
443
|
+
'volatility': returns.std() * np.sqrt(252),
|
444
|
+
'var_95': returns.quantile(0.05),
|
445
|
+
'max_drawdown': (data['close'] / data['close'].expanding(min_periods=1).max() - 1).min(),
|
446
|
+
'sharpe_ratio': (returns.mean() * 252) / (returns.std() * np.sqrt(252)),
|
447
|
+
'sortino_ratio': (returns.mean() * 252) / (returns[returns < 0].std() * np.sqrt(252))
|
448
|
+
})
|
449
|
+
|
450
|
+
return risk_metrics
|
451
|
+
|
452
|
+
except Exception as e:
|
453
|
+
logger.error(f"Error calculating risk metrics: {e}")
|
454
|
+
return {}
|
455
|
+
|
456
|
+
async def execute(self, agent: Agent, **kwargs) -> Dict[str, MarketIntelligence]:
|
457
|
+
"""Execute comprehensive market intelligence gathering."""
|
458
|
+
try:
|
459
|
+
symbols = kwargs['symbols']
|
460
|
+
asset_types = kwargs['asset_types']
|
461
|
+
data_types = kwargs.get('data_types', ['all'])
|
462
|
+
|
463
|
+
results = {}
|
464
|
+
|
465
|
+
for symbol, asset_type in zip(symbols, asset_types):
|
466
|
+
# Get current price
|
467
|
+
current_price = await self._get_current_price(symbol, asset_type)
|
468
|
+
|
469
|
+
# Gather intelligence components
|
470
|
+
market_news = await self._fetch_market_news(symbol, asset_type)
|
471
|
+
social_metrics = await self._fetch_social_metrics(symbol, asset_type)
|
472
|
+
fundamental_metrics = await self._fetch_fundamental_data(symbol, asset_type)
|
473
|
+
analyst_ratings = await self._fetch_analyst_ratings(symbol, asset_type)
|
474
|
+
market_events = await self._fetch_market_events(symbol, asset_type)
|
475
|
+
regulatory_updates = await self._fetch_regulatory_updates(symbol, asset_type)
|
476
|
+
competitor_analysis = await self._analyze_competitors(symbol, asset_type)
|
477
|
+
risk_metrics = await self._calculate_risk_metrics(symbol, asset_type)
|
478
|
+
|
479
|
+
# Combine all intelligence
|
480
|
+
results[symbol] = MarketIntelligence(
|
481
|
+
symbol=symbol,
|
482
|
+
asset_type=asset_type,
|
483
|
+
timestamp=datetime.now(),
|
484
|
+
current_price=current_price,
|
485
|
+
market_news=market_news,
|
486
|
+
social_metrics=social_metrics,
|
487
|
+
fundamental_metrics=fundamental_metrics,
|
488
|
+
analyst_ratings=analyst_ratings,
|
489
|
+
market_events=market_events,
|
490
|
+
regulatory_updates=regulatory_updates,
|
491
|
+
competitor_analysis=competitor_analysis,
|
492
|
+
risk_metrics=risk_metrics
|
493
|
+
)
|
494
|
+
|
495
|
+
return results
|
496
|
+
|
497
|
+
except Exception as e:
|
498
|
+
logger.error(f"Error executing market intelligence gathering: {e}")
|
499
|
+
raise
|
500
|
+
|
501
|
+
def validate_arguments(self, **kwargs) -> bool:
|
502
|
+
"""Validate the provided arguments."""
|
503
|
+
try:
|
504
|
+
required_args = [arg.name for arg in self.arguments if arg.required]
|
505
|
+
for arg in required_args:
|
506
|
+
if arg not in kwargs:
|
507
|
+
raise ValueError(f"Missing required argument: {arg}")
|
508
|
+
|
509
|
+
if len(kwargs['symbols']) != len(kwargs['asset_types']):
|
510
|
+
raise ValueError("Number of symbols must match number of asset types")
|
511
|
+
|
512
|
+
valid_asset_types = ['crypto', 'stock', 'index']
|
513
|
+
invalid_types = set(kwargs['asset_types']) - set(valid_asset_types)
|
514
|
+
if invalid_types:
|
515
|
+
raise ValueError(f"Invalid asset types: {invalid_types}")
|
516
|
+
|
517
|
+
return True
|
518
|
+
except Exception as e:
|
519
|
+
logger.error(f"Argument validation error: {e}")
|
520
|
+
return False
|