sigma-terminal 2.0.1__py3-none-any.whl → 3.2.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.
sigma/tools/backtest.py CHANGED
@@ -907,13 +907,13 @@ def run_lean_backtest(
907
907
  result["status"] = "error"
908
908
  result["error"] = str(e)
909
909
 
910
- # Provide QuantConnect instructions for institutional-grade backtest
910
+ # Provide QuantConnect instructions for advanced backtest
911
911
  result["quantconnect_instructions"] = [
912
- "For INSTITUTIONAL-GRADE backtesting with full market data:",
912
+ "For ADVANCED backtesting with full market data:",
913
913
  "1. Go to https://www.quantconnect.com (FREE account)",
914
914
  "2. Click 'Algorithm Lab' -> 'Create New Algorithm'",
915
915
  f"3. Paste the code from: {algo_file}",
916
- "4. Click 'Backtest' for institutional data!"
916
+ "4. Click 'Backtest' for professional data!"
917
917
  ]
918
918
 
919
919
  result["algorithm_ready"] = True
@@ -930,7 +930,7 @@ def run_simple_backtest(
930
930
  ) -> dict[str, Any]:
931
931
  """Run a comprehensive Python backtest using yfinance data.
932
932
 
933
- This provides institutional-quality results with detailed metrics,
933
+ This provides professional-quality results with detailed metrics,
934
934
  charts, and analysis. For even more accurate results, use QuantConnect.
935
935
  """
936
936
  import yfinance as yf
@@ -1176,10 +1176,16 @@ def run_simple_backtest(
1176
1176
  result["trades"] = trades[-15:] # Last 15 trades
1177
1177
  result["monthly_returns"] = monthly_pcts[-12:] # Last 12 months
1178
1178
 
1179
- # Generate charts
1179
+ # Generate charts - IMPORTANT: fully clear plotext state before each chart
1180
1180
  try:
1181
+ import plotext as plt
1182
+
1181
1183
  # 1. Equity Curve Chart
1184
+ plt.clf()
1185
+ plt.clt()
1186
+ plt.cld()
1182
1187
  plt.clear_figure()
1188
+ plt.clear_data()
1183
1189
  plt.plotsize(120, 25)
1184
1190
  plt.theme("dark")
1185
1191
  plt.canvas_color("black")
@@ -1205,7 +1211,11 @@ def run_simple_backtest(
1205
1211
  result["charts"]["equity_curve"] = plt.build()
1206
1212
 
1207
1213
  # 2. Drawdown Chart
1214
+ plt.clf()
1215
+ plt.clt()
1216
+ plt.cld()
1208
1217
  plt.clear_figure()
1218
+ plt.clear_data()
1209
1219
  plt.plotsize(120, 15)
1210
1220
  plt.theme("dark")
1211
1221
  plt.canvas_color("black")
@@ -1219,7 +1229,11 @@ def run_simple_backtest(
1219
1229
 
1220
1230
  # 3. Trade Distribution
1221
1231
  if sell_trades:
1232
+ plt.clf()
1233
+ plt.clt()
1234
+ plt.cld()
1222
1235
  plt.clear_figure()
1236
+ plt.clear_data()
1223
1237
  plt.plotsize(80, 12)
1224
1238
  plt.theme("dark")
1225
1239
  plt.canvas_color("black")
@@ -1235,7 +1249,11 @@ def run_simple_backtest(
1235
1249
 
1236
1250
  # 4. Monthly Returns Bar Chart
1237
1251
  if monthly_pcts:
1252
+ plt.clf()
1253
+ plt.clt()
1254
+ plt.cld()
1238
1255
  plt.clear_figure()
1256
+ plt.clear_data()
1239
1257
  plt.plotsize(100, 12)
1240
1258
  plt.theme("dark")
1241
1259
  plt.canvas_color("black")
sigma/tools.py ADDED
@@ -0,0 +1,617 @@
1
+ """Financial data tools for Sigma."""
2
+
3
+ import json
4
+ from datetime import datetime, timedelta
5
+ from typing import Any, Optional
6
+
7
+ import yfinance as yf
8
+ import pandas as pd
9
+ import numpy as np
10
+
11
+
12
+ # ============================================================================
13
+ # STOCK DATA TOOLS
14
+ # ============================================================================
15
+
16
+ def get_stock_quote(symbol: str) -> dict:
17
+ """Get current stock quote with key metrics."""
18
+ try:
19
+ ticker = yf.Ticker(symbol.upper())
20
+ info = ticker.info
21
+
22
+ return {
23
+ "symbol": symbol.upper(),
24
+ "name": info.get("shortName", "N/A"),
25
+ "price": info.get("regularMarketPrice", 0),
26
+ "change": info.get("regularMarketChange", 0),
27
+ "change_percent": info.get("regularMarketChangePercent", 0),
28
+ "open": info.get("regularMarketOpen", 0),
29
+ "high": info.get("regularMarketDayHigh", 0),
30
+ "low": info.get("regularMarketDayLow", 0),
31
+ "volume": info.get("regularMarketVolume", 0),
32
+ "market_cap": info.get("marketCap", 0),
33
+ "pe_ratio": info.get("trailingPE", "N/A"),
34
+ "52w_high": info.get("fiftyTwoWeekHigh", 0),
35
+ "52w_low": info.get("fiftyTwoWeekLow", 0),
36
+ "avg_volume": info.get("averageVolume", 0),
37
+ }
38
+ except Exception as e:
39
+ return {"error": str(e), "symbol": symbol}
40
+
41
+
42
+ def get_stock_history(symbol: str, period: str = "3mo", interval: str = "1d") -> dict:
43
+ """Get historical price data."""
44
+ try:
45
+ ticker = yf.Ticker(symbol.upper())
46
+ hist = ticker.history(period=period, interval=interval)
47
+
48
+ if hist.empty:
49
+ return {"error": "No data found", "symbol": symbol}
50
+
51
+ # Calculate basic stats
52
+ returns = hist["Close"].pct_change().dropna()
53
+
54
+ return {
55
+ "symbol": symbol.upper(),
56
+ "period": period,
57
+ "data_points": len(hist),
58
+ "start_date": str(hist.index[0].date()),
59
+ "end_date": str(hist.index[-1].date()),
60
+ "start_price": round(hist["Close"].iloc[0], 2),
61
+ "end_price": round(hist["Close"].iloc[-1], 2),
62
+ "high": round(hist["High"].max(), 2),
63
+ "low": round(hist["Low"].min(), 2),
64
+ "total_return": round((hist["Close"].iloc[-1] / hist["Close"].iloc[0] - 1) * 100, 2),
65
+ "volatility": round(returns.std() * np.sqrt(252) * 100, 2),
66
+ "avg_volume": int(hist["Volume"].mean()),
67
+ }
68
+ except Exception as e:
69
+ return {"error": str(e), "symbol": symbol}
70
+
71
+
72
+ def get_company_info(symbol: str) -> dict:
73
+ """Get detailed company information."""
74
+ try:
75
+ ticker = yf.Ticker(symbol.upper())
76
+ info = ticker.info
77
+
78
+ return {
79
+ "symbol": symbol.upper(),
80
+ "name": info.get("longName", info.get("shortName", "N/A")),
81
+ "sector": info.get("sector", "N/A"),
82
+ "industry": info.get("industry", "N/A"),
83
+ "country": info.get("country", "N/A"),
84
+ "website": info.get("website", "N/A"),
85
+ "employees": info.get("fullTimeEmployees", "N/A"),
86
+ "description": info.get("longBusinessSummary", "N/A")[:500] + "..." if info.get("longBusinessSummary") else "N/A",
87
+ "market_cap": info.get("marketCap", 0),
88
+ "enterprise_value": info.get("enterpriseValue", 0),
89
+ }
90
+ except Exception as e:
91
+ return {"error": str(e), "symbol": symbol}
92
+
93
+
94
+ def get_financial_statements(symbol: str, statement: str = "income") -> dict:
95
+ """Get financial statements (income, balance, cash)."""
96
+ try:
97
+ ticker = yf.Ticker(symbol.upper())
98
+
99
+ if statement == "income":
100
+ df = ticker.income_stmt
101
+ elif statement == "balance":
102
+ df = ticker.balance_sheet
103
+ elif statement == "cash":
104
+ df = ticker.cashflow
105
+ else:
106
+ return {"error": f"Unknown statement type: {statement}"}
107
+
108
+ if df.empty:
109
+ return {"error": "No data found", "symbol": symbol}
110
+
111
+ # Get latest period
112
+ latest = df.iloc[:, 0]
113
+
114
+ # Convert to dict with formatted numbers
115
+ data = {}
116
+ for idx, val in latest.items():
117
+ if pd.notna(val):
118
+ if abs(val) >= 1e9:
119
+ data[str(idx)] = f"${val/1e9:.2f}B"
120
+ elif abs(val) >= 1e6:
121
+ data[str(idx)] = f"${val/1e6:.2f}M"
122
+ else:
123
+ data[str(idx)] = f"${val:,.0f}"
124
+
125
+ return {
126
+ "symbol": symbol.upper(),
127
+ "statement_type": statement,
128
+ "period": str(df.columns[0].date()) if hasattr(df.columns[0], 'date') else str(df.columns[0]),
129
+ "data": data
130
+ }
131
+ except Exception as e:
132
+ return {"error": str(e), "symbol": symbol}
133
+
134
+
135
+ def get_analyst_recommendations(symbol: str) -> dict:
136
+ """Get analyst recommendations and price targets."""
137
+ try:
138
+ ticker = yf.Ticker(symbol.upper())
139
+
140
+ # Get recommendations
141
+ recs = ticker.recommendations
142
+ if recs is not None and not recs.empty:
143
+ recent = recs.tail(10)
144
+ rec_summary = recent["To Grade"].value_counts().to_dict() if "To Grade" in recent.columns else {}
145
+ else:
146
+ rec_summary = {}
147
+
148
+ # Get info for targets
149
+ info = ticker.info
150
+
151
+ return {
152
+ "symbol": symbol.upper(),
153
+ "recommendation": info.get("recommendationKey", "N/A"),
154
+ "target_high": info.get("targetHighPrice", "N/A"),
155
+ "target_low": info.get("targetLowPrice", "N/A"),
156
+ "target_mean": info.get("targetMeanPrice", "N/A"),
157
+ "target_median": info.get("targetMedianPrice", "N/A"),
158
+ "num_analysts": info.get("numberOfAnalystOpinions", "N/A"),
159
+ "recent_grades": rec_summary
160
+ }
161
+ except Exception as e:
162
+ return {"error": str(e), "symbol": symbol}
163
+
164
+
165
+ def get_insider_trades(symbol: str) -> dict:
166
+ """Get recent insider trading activity."""
167
+ try:
168
+ ticker = yf.Ticker(symbol.upper())
169
+ insiders = ticker.insider_transactions
170
+
171
+ if insiders is None or insiders.empty:
172
+ return {"symbol": symbol.upper(), "trades": [], "message": "No recent insider trades"}
173
+
174
+ trades = []
175
+ for _, row in insiders.head(10).iterrows():
176
+ trades.append({
177
+ "date": str(row.get("Start Date", ""))[:10],
178
+ "insider": row.get("Insider", "N/A"),
179
+ "position": row.get("Position", "N/A"),
180
+ "transaction": row.get("Transaction", "N/A"),
181
+ "shares": row.get("Shares", 0),
182
+ "value": row.get("Value", 0),
183
+ })
184
+
185
+ return {
186
+ "symbol": symbol.upper(),
187
+ "trades": trades
188
+ }
189
+ except Exception as e:
190
+ return {"error": str(e), "symbol": symbol}
191
+
192
+
193
+ def get_institutional_holders(symbol: str) -> dict:
194
+ """Get institutional ownership data."""
195
+ try:
196
+ ticker = yf.Ticker(symbol.upper())
197
+ holders = ticker.institutional_holders
198
+
199
+ if holders is None or holders.empty:
200
+ return {"symbol": symbol.upper(), "holders": []}
201
+
202
+ holder_list = []
203
+ for _, row in holders.head(10).iterrows():
204
+ holder_list.append({
205
+ "holder": row.get("Holder", "N/A"),
206
+ "shares": int(row.get("Shares", 0)),
207
+ "date_reported": str(row.get("Date Reported", ""))[:10],
208
+ "pct_held": round(row.get("% Out", 0) * 100, 2) if row.get("% Out") else 0,
209
+ "value": int(row.get("Value", 0)),
210
+ })
211
+
212
+ return {
213
+ "symbol": symbol.upper(),
214
+ "holders": holder_list
215
+ }
216
+ except Exception as e:
217
+ return {"error": str(e), "symbol": symbol}
218
+
219
+
220
+ # ============================================================================
221
+ # TECHNICAL ANALYSIS TOOLS
222
+ # ============================================================================
223
+
224
+ def technical_analysis(symbol: str, period: str = "6mo") -> dict:
225
+ """Perform comprehensive technical analysis."""
226
+ try:
227
+ ticker = yf.Ticker(symbol.upper())
228
+ hist = ticker.history(period=period)
229
+
230
+ if hist.empty:
231
+ return {"error": "No data found", "symbol": symbol}
232
+
233
+ close = hist["Close"]
234
+ high = hist["High"]
235
+ low = hist["Low"]
236
+ volume = hist["Volume"]
237
+
238
+ # Moving averages
239
+ sma_20 = close.rolling(20).mean().iloc[-1]
240
+ sma_50 = close.rolling(50).mean().iloc[-1]
241
+ sma_200 = close.rolling(200).mean().iloc[-1] if len(close) >= 200 else None
242
+ ema_12 = close.ewm(span=12).mean().iloc[-1]
243
+ ema_26 = close.ewm(span=26).mean().iloc[-1]
244
+
245
+ # RSI
246
+ delta = close.diff()
247
+ gain = (delta.where(delta > 0, 0)).rolling(14).mean()
248
+ loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
249
+ rs = gain / loss
250
+ rsi = (100 - (100 / (1 + rs))).iloc[-1]
251
+
252
+ # MACD
253
+ macd = ema_12 - ema_26
254
+ signal = close.ewm(span=9).mean().iloc[-1]
255
+
256
+ # Bollinger Bands
257
+ bb_mid = close.rolling(20).mean().iloc[-1]
258
+ bb_std = close.rolling(20).std().iloc[-1]
259
+ bb_upper = bb_mid + (bb_std * 2)
260
+ bb_lower = bb_mid - (bb_std * 2)
261
+
262
+ # Support/Resistance (simple)
263
+ recent_high = high.tail(20).max()
264
+ recent_low = low.tail(20).min()
265
+
266
+ # Volume analysis
267
+ avg_vol = volume.mean()
268
+ recent_vol = volume.tail(5).mean()
269
+ vol_trend = "Above Average" if recent_vol > avg_vol else "Below Average"
270
+
271
+ current_price = close.iloc[-1]
272
+
273
+ # Generate signals
274
+ signals = []
275
+ if current_price > sma_20:
276
+ signals.append("Above SMA20 (Bullish)")
277
+ else:
278
+ signals.append("Below SMA20 (Bearish)")
279
+
280
+ if current_price > sma_50:
281
+ signals.append("Above SMA50 (Bullish)")
282
+ else:
283
+ signals.append("Below SMA50 (Bearish)")
284
+
285
+ if rsi > 70:
286
+ signals.append("RSI Overbought (>70)")
287
+ elif rsi < 30:
288
+ signals.append("RSI Oversold (<30)")
289
+ else:
290
+ signals.append(f"RSI Neutral ({rsi:.1f})")
291
+
292
+ if macd > signal:
293
+ signals.append("MACD Bullish Crossover")
294
+ else:
295
+ signals.append("MACD Bearish")
296
+
297
+ return {
298
+ "symbol": symbol.upper(),
299
+ "current_price": round(current_price, 2),
300
+ "indicators": {
301
+ "sma_20": round(sma_20, 2),
302
+ "sma_50": round(sma_50, 2),
303
+ "sma_200": round(sma_200, 2) if sma_200 else "N/A",
304
+ "ema_12": round(ema_12, 2),
305
+ "ema_26": round(ema_26, 2),
306
+ "rsi": round(rsi, 2),
307
+ "macd": round(macd, 4),
308
+ "bb_upper": round(bb_upper, 2),
309
+ "bb_mid": round(bb_mid, 2),
310
+ "bb_lower": round(bb_lower, 2),
311
+ },
312
+ "support_resistance": {
313
+ "resistance": round(recent_high, 2),
314
+ "support": round(recent_low, 2),
315
+ },
316
+ "volume": {
317
+ "average": int(avg_vol),
318
+ "recent": int(recent_vol),
319
+ "trend": vol_trend,
320
+ },
321
+ "signals": signals,
322
+ }
323
+ except Exception as e:
324
+ return {"error": str(e), "symbol": symbol}
325
+
326
+
327
+ # ============================================================================
328
+ # COMPARISON & MARKET TOOLS
329
+ # ============================================================================
330
+
331
+ def compare_stocks(symbols: list[str], period: str = "1y") -> dict:
332
+ """Compare multiple stocks."""
333
+ try:
334
+ results = []
335
+
336
+ for symbol in symbols[:5]: # Limit to 5
337
+ ticker = yf.Ticker(symbol.upper())
338
+ hist = ticker.history(period=period)
339
+ info = ticker.info
340
+
341
+ if hist.empty:
342
+ continue
343
+
344
+ returns = hist["Close"].pct_change().dropna()
345
+ total_return = (hist["Close"].iloc[-1] / hist["Close"].iloc[0] - 1) * 100
346
+
347
+ results.append({
348
+ "symbol": symbol.upper(),
349
+ "name": info.get("shortName", "N/A"),
350
+ "price": round(hist["Close"].iloc[-1], 2),
351
+ "total_return": round(total_return, 2),
352
+ "volatility": round(returns.std() * np.sqrt(252) * 100, 2),
353
+ "sharpe": round((returns.mean() * 252) / (returns.std() * np.sqrt(252)), 2) if returns.std() > 0 else 0,
354
+ "market_cap": info.get("marketCap", 0),
355
+ "pe_ratio": info.get("trailingPE", "N/A"),
356
+ })
357
+
358
+ # Sort by return
359
+ results.sort(key=lambda x: x["total_return"], reverse=True)
360
+
361
+ return {
362
+ "period": period,
363
+ "comparison": results,
364
+ "best_performer": results[0]["symbol"] if results else None,
365
+ "worst_performer": results[-1]["symbol"] if results else None,
366
+ }
367
+ except Exception as e:
368
+ return {"error": str(e)}
369
+
370
+
371
+ def get_market_overview() -> dict:
372
+ """Get market overview with major indices."""
373
+ indices = {
374
+ "^GSPC": "S&P 500",
375
+ "^DJI": "Dow Jones",
376
+ "^IXIC": "NASDAQ",
377
+ "^RUT": "Russell 2000",
378
+ "^VIX": "VIX",
379
+ }
380
+
381
+ results = []
382
+ for symbol, name in indices.items():
383
+ try:
384
+ ticker = yf.Ticker(symbol)
385
+ info = ticker.info
386
+ results.append({
387
+ "symbol": symbol,
388
+ "name": name,
389
+ "price": info.get("regularMarketPrice", 0),
390
+ "change": info.get("regularMarketChange", 0),
391
+ "change_percent": info.get("regularMarketChangePercent", 0),
392
+ })
393
+ except:
394
+ continue
395
+
396
+ return {"indices": results, "timestamp": datetime.now().isoformat()}
397
+
398
+
399
+ def get_sector_performance() -> dict:
400
+ """Get sector ETF performance."""
401
+ sectors = {
402
+ "XLK": "Technology",
403
+ "XLF": "Financials",
404
+ "XLV": "Healthcare",
405
+ "XLE": "Energy",
406
+ "XLI": "Industrials",
407
+ "XLY": "Consumer Discretionary",
408
+ "XLP": "Consumer Staples",
409
+ "XLU": "Utilities",
410
+ "XLB": "Materials",
411
+ "XLRE": "Real Estate",
412
+ }
413
+
414
+ results = []
415
+ for symbol, name in sectors.items():
416
+ try:
417
+ ticker = yf.Ticker(symbol)
418
+ info = ticker.info
419
+ results.append({
420
+ "symbol": symbol,
421
+ "sector": name,
422
+ "price": info.get("regularMarketPrice", 0),
423
+ "change_percent": round(info.get("regularMarketChangePercent", 0), 2),
424
+ })
425
+ except:
426
+ continue
427
+
428
+ # Sort by performance
429
+ results.sort(key=lambda x: x["change_percent"], reverse=True)
430
+
431
+ return {"sectors": results, "timestamp": datetime.now().isoformat()}
432
+
433
+
434
+ # ============================================================================
435
+ # TOOL DEFINITIONS FOR LLM
436
+ # ============================================================================
437
+
438
+ TOOLS = [
439
+ {
440
+ "type": "function",
441
+ "function": {
442
+ "name": "get_stock_quote",
443
+ "description": "Get current stock quote with price, change, volume, and key metrics",
444
+ "parameters": {
445
+ "type": "object",
446
+ "properties": {
447
+ "symbol": {"type": "string", "description": "Stock ticker symbol (e.g., AAPL, MSFT)"}
448
+ },
449
+ "required": ["symbol"]
450
+ }
451
+ }
452
+ },
453
+ {
454
+ "type": "function",
455
+ "function": {
456
+ "name": "get_stock_history",
457
+ "description": "Get historical price data and returns for a stock",
458
+ "parameters": {
459
+ "type": "object",
460
+ "properties": {
461
+ "symbol": {"type": "string", "description": "Stock ticker symbol"},
462
+ "period": {"type": "string", "description": "Time period: 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, max", "default": "3mo"}
463
+ },
464
+ "required": ["symbol"]
465
+ }
466
+ }
467
+ },
468
+ {
469
+ "type": "function",
470
+ "function": {
471
+ "name": "get_company_info",
472
+ "description": "Get detailed company information including sector, industry, and description",
473
+ "parameters": {
474
+ "type": "object",
475
+ "properties": {
476
+ "symbol": {"type": "string", "description": "Stock ticker symbol"}
477
+ },
478
+ "required": ["symbol"]
479
+ }
480
+ }
481
+ },
482
+ {
483
+ "type": "function",
484
+ "function": {
485
+ "name": "get_financial_statements",
486
+ "description": "Get financial statements (income statement, balance sheet, or cash flow)",
487
+ "parameters": {
488
+ "type": "object",
489
+ "properties": {
490
+ "symbol": {"type": "string", "description": "Stock ticker symbol"},
491
+ "statement": {"type": "string", "enum": ["income", "balance", "cash"], "description": "Type of statement", "default": "income"}
492
+ },
493
+ "required": ["symbol"]
494
+ }
495
+ }
496
+ },
497
+ {
498
+ "type": "function",
499
+ "function": {
500
+ "name": "get_analyst_recommendations",
501
+ "description": "Get analyst recommendations, price targets, and ratings",
502
+ "parameters": {
503
+ "type": "object",
504
+ "properties": {
505
+ "symbol": {"type": "string", "description": "Stock ticker symbol"}
506
+ },
507
+ "required": ["symbol"]
508
+ }
509
+ }
510
+ },
511
+ {
512
+ "type": "function",
513
+ "function": {
514
+ "name": "get_insider_trades",
515
+ "description": "Get recent insider trading activity",
516
+ "parameters": {
517
+ "type": "object",
518
+ "properties": {
519
+ "symbol": {"type": "string", "description": "Stock ticker symbol"}
520
+ },
521
+ "required": ["symbol"]
522
+ }
523
+ }
524
+ },
525
+ {
526
+ "type": "function",
527
+ "function": {
528
+ "name": "get_institutional_holders",
529
+ "description": "Get institutional ownership and major shareholders",
530
+ "parameters": {
531
+ "type": "object",
532
+ "properties": {
533
+ "symbol": {"type": "string", "description": "Stock ticker symbol"}
534
+ },
535
+ "required": ["symbol"]
536
+ }
537
+ }
538
+ },
539
+ {
540
+ "type": "function",
541
+ "function": {
542
+ "name": "technical_analysis",
543
+ "description": "Perform comprehensive technical analysis with indicators (RSI, MACD, Moving Averages, Bollinger Bands)",
544
+ "parameters": {
545
+ "type": "object",
546
+ "properties": {
547
+ "symbol": {"type": "string", "description": "Stock ticker symbol"},
548
+ "period": {"type": "string", "description": "Analysis period", "default": "6mo"}
549
+ },
550
+ "required": ["symbol"]
551
+ }
552
+ }
553
+ },
554
+ {
555
+ "type": "function",
556
+ "function": {
557
+ "name": "compare_stocks",
558
+ "description": "Compare multiple stocks on returns, volatility, and metrics",
559
+ "parameters": {
560
+ "type": "object",
561
+ "properties": {
562
+ "symbols": {"type": "array", "items": {"type": "string"}, "description": "List of stock symbols to compare"},
563
+ "period": {"type": "string", "description": "Comparison period", "default": "1y"}
564
+ },
565
+ "required": ["symbols"]
566
+ }
567
+ }
568
+ },
569
+ {
570
+ "type": "function",
571
+ "function": {
572
+ "name": "get_market_overview",
573
+ "description": "Get overview of major market indices (S&P 500, Dow, NASDAQ, etc.)",
574
+ "parameters": {
575
+ "type": "object",
576
+ "properties": {},
577
+ "required": []
578
+ }
579
+ }
580
+ },
581
+ {
582
+ "type": "function",
583
+ "function": {
584
+ "name": "get_sector_performance",
585
+ "description": "Get performance of market sectors",
586
+ "parameters": {
587
+ "type": "object",
588
+ "properties": {},
589
+ "required": []
590
+ }
591
+ }
592
+ },
593
+ ]
594
+
595
+
596
+ # Tool executor
597
+ TOOL_FUNCTIONS = {
598
+ "get_stock_quote": get_stock_quote,
599
+ "get_stock_history": get_stock_history,
600
+ "get_company_info": get_company_info,
601
+ "get_financial_statements": get_financial_statements,
602
+ "get_analyst_recommendations": get_analyst_recommendations,
603
+ "get_insider_trades": get_insider_trades,
604
+ "get_institutional_holders": get_institutional_holders,
605
+ "technical_analysis": technical_analysis,
606
+ "compare_stocks": compare_stocks,
607
+ "get_market_overview": get_market_overview,
608
+ "get_sector_performance": get_sector_performance,
609
+ }
610
+
611
+
612
+ def execute_tool(name: str, args: dict) -> Any:
613
+ """Execute a tool by name."""
614
+ func = TOOL_FUNCTIONS.get(name)
615
+ if func:
616
+ return func(**args)
617
+ return {"error": f"Unknown tool: {name}"}