sigma-terminal 3.4.0__py3-none-any.whl → 3.5.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.
@@ -962,6 +962,466 @@ def search_earnings_transcripts(company: str, num_results: int = 3) -> dict:
962
962
  return {"error": str(e)}
963
963
 
964
964
 
965
+ # ============================================================================
966
+ # CHART GENERATION TOOLS
967
+ # ============================================================================
968
+
969
+ def generate_stock_chart(symbol: str, period: str = "6mo", chart_type: str = "candlestick",
970
+ show_volume: bool = True, show_indicators: bool = True) -> dict:
971
+ """Generate a stock chart and save it to file."""
972
+ try:
973
+ from .charts import create_candlestick_chart, create_line_chart, create_technical_chart
974
+
975
+ ticker = yf.Ticker(symbol.upper())
976
+ data = ticker.history(period=period)
977
+
978
+ if data.empty:
979
+ return {"error": f"No data found for {symbol}", "symbol": symbol}
980
+
981
+ # Generate chart based on type
982
+ if chart_type == "candlestick":
983
+ chart_path = create_candlestick_chart(
984
+ symbol=symbol,
985
+ data=data,
986
+ show_volume=show_volume,
987
+ show_sma=show_indicators
988
+ )
989
+ elif chart_type == "line":
990
+ chart_path = create_line_chart(
991
+ symbol=symbol,
992
+ data=data,
993
+ show_volume=show_volume
994
+ )
995
+ elif chart_type == "technical":
996
+ chart_path = create_technical_chart(
997
+ symbol=symbol,
998
+ data=data,
999
+ indicators=["rsi", "macd"] if show_indicators else []
1000
+ )
1001
+ else:
1002
+ chart_path = create_candlestick_chart(symbol=symbol, data=data)
1003
+
1004
+ return {
1005
+ "symbol": symbol.upper(),
1006
+ "chart_type": chart_type,
1007
+ "period": period,
1008
+ "chart_path": chart_path,
1009
+ "message": f"Chart generated and saved to: {chart_path}",
1010
+ "data_points": len(data),
1011
+ "start_date": str(data.index[0].date()),
1012
+ "end_date": str(data.index[-1].date()),
1013
+ }
1014
+ except Exception as e:
1015
+ return {"error": str(e), "symbol": symbol}
1016
+
1017
+
1018
+ def generate_comparison_chart(symbols: list, period: str = "1y", normalize: bool = True) -> dict:
1019
+ """Generate a comparison chart for multiple stocks."""
1020
+ try:
1021
+ from .charts import create_comparison_chart
1022
+
1023
+ data_dict = {}
1024
+ for symbol in symbols:
1025
+ ticker = yf.Ticker(symbol.upper())
1026
+ data = ticker.history(period=period)
1027
+ if not data.empty:
1028
+ data_dict[symbol.upper()] = data
1029
+
1030
+ if not data_dict:
1031
+ return {"error": "No data found for any symbols", "symbols": symbols}
1032
+
1033
+ chart_path = create_comparison_chart(
1034
+ symbols=[s.upper() for s in symbols],
1035
+ data_dict=data_dict,
1036
+ normalize=normalize
1037
+ )
1038
+
1039
+ return {
1040
+ "symbols": list(data_dict.keys()),
1041
+ "period": period,
1042
+ "normalized": normalize,
1043
+ "chart_path": chart_path,
1044
+ "message": f"Comparison chart saved to: {chart_path}"
1045
+ }
1046
+ except Exception as e:
1047
+ return {"error": str(e)}
1048
+
1049
+
1050
+ # ============================================================================
1051
+ # ADVANCED ANALYSIS TOOLS
1052
+ # ============================================================================
1053
+
1054
+ def get_valuation_metrics(symbol: str) -> dict:
1055
+ """Get comprehensive valuation metrics for a stock."""
1056
+ try:
1057
+ ticker = yf.Ticker(symbol.upper())
1058
+ info = ticker.info
1059
+
1060
+ # Calculate valuation ratios
1061
+ pe_ratio = info.get("trailingPE", None)
1062
+ forward_pe = info.get("forwardPE", None)
1063
+ peg_ratio = info.get("pegRatio", None)
1064
+ pb_ratio = info.get("priceToBook", None)
1065
+ ps_ratio = info.get("priceToSalesTrailing12Months", None)
1066
+ ev_ebitda = info.get("enterpriseToEbitda", None)
1067
+ ev_revenue = info.get("enterpriseToRevenue", None)
1068
+
1069
+ # Get growth metrics
1070
+ earnings_growth = info.get("earningsGrowth", None)
1071
+ revenue_growth = info.get("revenueGrowth", None)
1072
+
1073
+ # Get profitability
1074
+ profit_margin = info.get("profitMargins", None)
1075
+ operating_margin = info.get("operatingMargins", None)
1076
+ roe = info.get("returnOnEquity", None)
1077
+ roa = info.get("returnOnAssets", None)
1078
+
1079
+ # Determine valuation assessment
1080
+ assessment = "FAIR"
1081
+ if pe_ratio and forward_pe:
1082
+ if pe_ratio > 30 and forward_pe > 25:
1083
+ assessment = "EXPENSIVE"
1084
+ elif pe_ratio < 15 and forward_pe < 12:
1085
+ assessment = "CHEAP"
1086
+
1087
+ return {
1088
+ "symbol": symbol.upper(),
1089
+ "name": info.get("shortName", symbol),
1090
+ "valuation": {
1091
+ "pe_ratio": round(pe_ratio, 2) if pe_ratio else "N/A",
1092
+ "forward_pe": round(forward_pe, 2) if forward_pe else "N/A",
1093
+ "peg_ratio": round(peg_ratio, 2) if peg_ratio else "N/A",
1094
+ "price_to_book": round(pb_ratio, 2) if pb_ratio else "N/A",
1095
+ "price_to_sales": round(ps_ratio, 2) if ps_ratio else "N/A",
1096
+ "ev_to_ebitda": round(ev_ebitda, 2) if ev_ebitda else "N/A",
1097
+ "ev_to_revenue": round(ev_revenue, 2) if ev_revenue else "N/A",
1098
+ },
1099
+ "growth": {
1100
+ "earnings_growth": f"{earnings_growth*100:.1f}%" if earnings_growth else "N/A",
1101
+ "revenue_growth": f"{revenue_growth*100:.1f}%" if revenue_growth else "N/A",
1102
+ },
1103
+ "profitability": {
1104
+ "profit_margin": f"{profit_margin*100:.1f}%" if profit_margin else "N/A",
1105
+ "operating_margin": f"{operating_margin*100:.1f}%" if operating_margin else "N/A",
1106
+ "return_on_equity": f"{roe*100:.1f}%" if roe else "N/A",
1107
+ "return_on_assets": f"{roa*100:.1f}%" if roa else "N/A",
1108
+ },
1109
+ "assessment": assessment,
1110
+ }
1111
+ except Exception as e:
1112
+ return {"error": str(e), "symbol": symbol}
1113
+
1114
+
1115
+ def get_risk_metrics(symbol: str, period: str = "1y") -> dict:
1116
+ """Calculate comprehensive risk metrics for a stock."""
1117
+ try:
1118
+ ticker = yf.Ticker(symbol.upper())
1119
+ hist = ticker.history(period=period)
1120
+
1121
+ if hist.empty or len(hist) < 30:
1122
+ return {"error": "Insufficient data for risk analysis", "symbol": symbol}
1123
+
1124
+ # Calculate daily returns
1125
+ returns = hist["Close"].pct_change().dropna()
1126
+
1127
+ # Basic risk metrics
1128
+ volatility = returns.std() * np.sqrt(252) * 100
1129
+
1130
+ # Value at Risk (VaR) - 95% confidence
1131
+ var_95 = np.percentile(returns, 5) * 100
1132
+
1133
+ # Conditional VaR (Expected Shortfall)
1134
+ cvar_95 = returns[returns <= np.percentile(returns, 5)].mean() * 100
1135
+
1136
+ # Maximum Drawdown
1137
+ cumulative = (1 + returns).cumprod()
1138
+ peak = cumulative.cummax()
1139
+ drawdown = (cumulative - peak) / peak
1140
+ max_drawdown = drawdown.min() * 100
1141
+
1142
+ # Sharpe Ratio (assuming 0% risk-free rate)
1143
+ sharpe = (returns.mean() * 252) / (returns.std() * np.sqrt(252))
1144
+
1145
+ # Sortino Ratio
1146
+ negative_returns = returns[returns < 0]
1147
+ downside_std = negative_returns.std() * np.sqrt(252)
1148
+ sortino = (returns.mean() * 252) / downside_std if downside_std > 0 else 0
1149
+
1150
+ # Beta calculation vs SPY
1151
+ try:
1152
+ spy = yf.Ticker("SPY")
1153
+ spy_hist = spy.history(period=period)
1154
+ spy_returns = spy_hist["Close"].pct_change().dropna()
1155
+
1156
+ # Align dates
1157
+ common_dates = returns.index.intersection(spy_returns.index)
1158
+ if len(common_dates) > 30:
1159
+ stock_r = returns.loc[common_dates]
1160
+ spy_r = spy_returns.loc[common_dates]
1161
+
1162
+ covariance = np.cov(stock_r, spy_r)[0, 1]
1163
+ spy_variance = np.var(spy_r)
1164
+ beta = covariance / spy_variance if spy_variance > 0 else 1.0
1165
+
1166
+ # Alpha (annualized)
1167
+ alpha = (returns.mean() * 252) - (beta * spy_returns.mean() * 252)
1168
+ else:
1169
+ beta = 1.0
1170
+ alpha = 0
1171
+ except:
1172
+ beta = 1.0
1173
+ alpha = 0
1174
+
1175
+ # Risk assessment
1176
+ risk_level = "MODERATE"
1177
+ if volatility > 40 or abs(max_drawdown) > 30:
1178
+ risk_level = "HIGH"
1179
+ elif volatility < 20 and abs(max_drawdown) < 15:
1180
+ risk_level = "LOW"
1181
+
1182
+ return {
1183
+ "symbol": symbol.upper(),
1184
+ "period": period,
1185
+ "volatility": {
1186
+ "annualized": f"{volatility:.2f}%",
1187
+ "daily": f"{returns.std()*100:.3f}%",
1188
+ },
1189
+ "drawdown": {
1190
+ "max_drawdown": f"{max_drawdown:.2f}%",
1191
+ "current_drawdown": f"{drawdown.iloc[-1]*100:.2f}%",
1192
+ },
1193
+ "value_at_risk": {
1194
+ "var_95": f"{var_95:.2f}%",
1195
+ "cvar_95": f"{cvar_95:.2f}%",
1196
+ },
1197
+ "ratios": {
1198
+ "sharpe": f"{sharpe:.2f}",
1199
+ "sortino": f"{sortino:.2f}",
1200
+ "beta": f"{beta:.2f}",
1201
+ "alpha": f"{alpha*100:.2f}%",
1202
+ },
1203
+ "risk_level": risk_level,
1204
+ }
1205
+ except Exception as e:
1206
+ return {"error": str(e), "symbol": symbol}
1207
+
1208
+
1209
+ def get_earnings_analysis(symbol: str) -> dict:
1210
+ """Get detailed earnings analysis including surprises and estimates."""
1211
+ try:
1212
+ ticker = yf.Ticker(symbol.upper())
1213
+ info = ticker.info
1214
+
1215
+ # Get earnings dates and history
1216
+ earnings_dates = ticker.earnings_dates
1217
+ earnings_history = ticker.earnings_history if hasattr(ticker, 'earnings_history') else None
1218
+
1219
+ # Build earnings data
1220
+ upcoming = None
1221
+ if earnings_dates is not None and not earnings_dates.empty:
1222
+ future_dates = earnings_dates[earnings_dates.index > pd.Timestamp.now()]
1223
+ if not future_dates.empty:
1224
+ next_date = future_dates.index[0]
1225
+ upcoming = {
1226
+ "date": str(next_date.date()) if hasattr(next_date, 'date') else str(next_date)[:10],
1227
+ "eps_estimate": future_dates.iloc[0].get("EPS Estimate", "N/A"),
1228
+ "revenue_estimate": future_dates.iloc[0].get("Revenue Estimate", "N/A"),
1229
+ }
1230
+
1231
+ # Get quarterly earnings
1232
+ quarterly_earnings = []
1233
+ if hasattr(ticker, 'quarterly_earnings') and ticker.quarterly_earnings is not None:
1234
+ qe = ticker.quarterly_earnings
1235
+ if not qe.empty:
1236
+ for date, row in qe.tail(4).iterrows():
1237
+ quarterly_earnings.append({
1238
+ "quarter": str(date),
1239
+ "revenue": row.get("Revenue", "N/A"),
1240
+ "earnings": row.get("Earnings", "N/A"),
1241
+ })
1242
+
1243
+ return {
1244
+ "symbol": symbol.upper(),
1245
+ "name": info.get("shortName", symbol),
1246
+ "eps_trailing": info.get("trailingEps", "N/A"),
1247
+ "eps_forward": info.get("forwardEps", "N/A"),
1248
+ "pe_ratio": info.get("trailingPE", "N/A"),
1249
+ "forward_pe": info.get("forwardPE", "N/A"),
1250
+ "upcoming_earnings": upcoming,
1251
+ "quarterly_history": quarterly_earnings,
1252
+ "earnings_growth": f"{info.get('earningsGrowth', 0)*100:.1f}%" if info.get('earningsGrowth') else "N/A",
1253
+ }
1254
+ except Exception as e:
1255
+ return {"error": str(e), "symbol": symbol}
1256
+
1257
+
1258
+ def get_dividend_analysis(symbol: str) -> dict:
1259
+ """Get comprehensive dividend analysis."""
1260
+ try:
1261
+ ticker = yf.Ticker(symbol.upper())
1262
+ info = ticker.info
1263
+
1264
+ # Get dividend data
1265
+ div_rate = info.get("dividendRate", 0)
1266
+ div_yield = info.get("dividendYield", 0)
1267
+ payout_ratio = info.get("payoutRatio", 0)
1268
+ ex_div_date = info.get("exDividendDate")
1269
+
1270
+ # Get dividend history
1271
+ dividends = ticker.dividends
1272
+ div_history = []
1273
+ if dividends is not None and not dividends.empty:
1274
+ for dt, amount in dividends.tail(8).items():
1275
+ date_str = str(dt.date()) if hasattr(dt, 'date') else str(dt)[:10] # type: ignore[union-attr]
1276
+ div_history.append({
1277
+ "date": date_str,
1278
+ "amount": f"${amount:.4f}",
1279
+ })
1280
+
1281
+ # Calculate dividend growth
1282
+ if len(dividends) >= 8:
1283
+ recent_divs = dividends.tail(4).sum()
1284
+ older_divs = dividends.tail(8).head(4).sum()
1285
+ div_growth = ((recent_divs / older_divs) - 1) * 100 if older_divs > 0 else 0
1286
+ else:
1287
+ div_growth = None
1288
+
1289
+ return {
1290
+ "symbol": symbol.upper(),
1291
+ "name": info.get("shortName", symbol),
1292
+ "dividend_rate": f"${div_rate:.2f}" if div_rate else "N/A",
1293
+ "dividend_yield": f"{div_yield*100:.2f}%" if div_yield else "N/A",
1294
+ "payout_ratio": f"{payout_ratio*100:.1f}%" if payout_ratio else "N/A",
1295
+ "ex_dividend_date": str(datetime.fromtimestamp(ex_div_date).date()) if ex_div_date else "N/A",
1296
+ "annual_dividend": f"${div_rate:.2f}" if div_rate else "N/A",
1297
+ "dividend_growth_yoy": f"{div_growth:.1f}%" if div_growth else "N/A",
1298
+ "history": div_history,
1299
+ }
1300
+ except Exception as e:
1301
+ return {"error": str(e), "symbol": symbol}
1302
+
1303
+
1304
+ def get_options_summary(symbol: str) -> dict:
1305
+ """Get options chain summary with key metrics."""
1306
+ try:
1307
+ ticker = yf.Ticker(symbol.upper())
1308
+
1309
+ # Get expiration dates
1310
+ expirations = ticker.options
1311
+ if not expirations:
1312
+ return {"error": "No options available", "symbol": symbol}
1313
+
1314
+ # Get nearest expiration
1315
+ nearest_exp = expirations[0]
1316
+ opt_chain = ticker.option_chain(nearest_exp)
1317
+
1318
+ calls = opt_chain.calls
1319
+ puts = opt_chain.puts
1320
+
1321
+ # Calculate put/call ratio
1322
+ total_call_volume = calls["volume"].sum() if "volume" in calls else 0
1323
+ total_put_volume = puts["volume"].sum() if "volume" in puts else 0
1324
+ pc_ratio = total_put_volume / total_call_volume if total_call_volume > 0 else 0
1325
+
1326
+ # Get ATM options
1327
+ current_price = ticker.info.get("regularMarketPrice", 0)
1328
+
1329
+ atm_call = calls.iloc[(calls["strike"] - current_price).abs().argsort()[:1]]
1330
+ atm_put = puts.iloc[(puts["strike"] - current_price).abs().argsort()[:1]]
1331
+
1332
+ # Implied volatility
1333
+ atm_call_iv = atm_call["impliedVolatility"].values[0] if not atm_call.empty else 0
1334
+ atm_put_iv = atm_put["impliedVolatility"].values[0] if not atm_put.empty else 0
1335
+ avg_iv = (atm_call_iv + atm_put_iv) / 2
1336
+
1337
+ return {
1338
+ "symbol": symbol.upper(),
1339
+ "current_price": f"${current_price:.2f}",
1340
+ "expirations_available": len(expirations),
1341
+ "nearest_expiration": nearest_exp,
1342
+ "put_call_ratio": f"{pc_ratio:.2f}",
1343
+ "implied_volatility": f"{avg_iv*100:.1f}%",
1344
+ "call_volume": int(total_call_volume) if total_call_volume else 0,
1345
+ "put_volume": int(total_put_volume) if total_put_volume else 0,
1346
+ "atm_call": {
1347
+ "strike": float(atm_call["strike"].values[0]) if not atm_call.empty else 0,
1348
+ "bid": float(atm_call["bid"].values[0]) if not atm_call.empty else 0,
1349
+ "ask": float(atm_call["ask"].values[0]) if not atm_call.empty else 0,
1350
+ "iv": f"{atm_call_iv*100:.1f}%",
1351
+ },
1352
+ "atm_put": {
1353
+ "strike": float(atm_put["strike"].values[0]) if not atm_put.empty else 0,
1354
+ "bid": float(atm_put["bid"].values[0]) if not atm_put.empty else 0,
1355
+ "ask": float(atm_put["ask"].values[0]) if not atm_put.empty else 0,
1356
+ "iv": f"{atm_put_iv*100:.1f}%",
1357
+ },
1358
+ "sentiment": "BEARISH" if pc_ratio > 1.2 else ("BULLISH" if pc_ratio < 0.7 else "NEUTRAL"),
1359
+ }
1360
+ except Exception as e:
1361
+ return {"error": str(e), "symbol": symbol}
1362
+
1363
+
1364
+ def get_peer_comparison(symbol: str) -> dict:
1365
+ """Compare a stock with its industry peers."""
1366
+ try:
1367
+ ticker = yf.Ticker(symbol.upper())
1368
+ info = ticker.info
1369
+
1370
+ # Get sector and find peers
1371
+ sector = info.get("sector", "")
1372
+ industry = info.get("industry", "")
1373
+
1374
+ # Define peer groups by industry
1375
+ tech_peers = ["AAPL", "MSFT", "GOOGL", "META", "AMZN"]
1376
+ semi_peers = ["NVDA", "AMD", "INTC", "AVGO", "QCOM"]
1377
+ finance_peers = ["JPM", "BAC", "GS", "MS", "C"]
1378
+ healthcare_peers = ["JNJ", "PFE", "UNH", "MRK", "ABBV"]
1379
+
1380
+ # Select peer group
1381
+ symbol_upper = symbol.upper()
1382
+ if symbol_upper in tech_peers or "Technology" in sector:
1383
+ peers = [p for p in tech_peers if p != symbol_upper][:4]
1384
+ elif symbol_upper in semi_peers or "Semiconductor" in industry:
1385
+ peers = [p for p in semi_peers if p != symbol_upper][:4]
1386
+ elif symbol_upper in finance_peers or "Financial" in sector:
1387
+ peers = [p for p in finance_peers if p != symbol_upper][:4]
1388
+ elif symbol_upper in healthcare_peers or "Healthcare" in sector:
1389
+ peers = [p for p in healthcare_peers if p != symbol_upper][:4]
1390
+ else:
1391
+ peers = []
1392
+
1393
+ # Get metrics for target and peers
1394
+ all_symbols = [symbol_upper] + peers
1395
+ comparison = []
1396
+
1397
+ for sym in all_symbols:
1398
+ try:
1399
+ t = yf.Ticker(sym)
1400
+ i = t.info
1401
+ comparison.append({
1402
+ "symbol": sym,
1403
+ "name": i.get("shortName", sym),
1404
+ "price": i.get("regularMarketPrice", 0),
1405
+ "market_cap": i.get("marketCap", 0),
1406
+ "pe_ratio": round(i.get("trailingPE", 0), 2) if i.get("trailingPE") else "N/A",
1407
+ "pb_ratio": round(i.get("priceToBook", 0), 2) if i.get("priceToBook") else "N/A",
1408
+ "dividend_yield": f"{i.get('dividendYield', 0)*100:.2f}%" if i.get("dividendYield") else "N/A",
1409
+ "profit_margin": f"{i.get('profitMargins', 0)*100:.1f}%" if i.get("profitMargins") else "N/A",
1410
+ })
1411
+ except:
1412
+ continue
1413
+
1414
+ return {
1415
+ "target": symbol.upper(),
1416
+ "sector": sector,
1417
+ "industry": industry,
1418
+ "peer_count": len(peers),
1419
+ "comparison": comparison,
1420
+ }
1421
+ except Exception as e:
1422
+ return {"error": str(e), "symbol": symbol}
1423
+
1424
+
965
1425
  # ============================================================================
966
1426
  # TOOL DEFINITIONS FOR LLM
967
1427
  # ============================================================================
@@ -990,7 +1450,8 @@ TOOLS = [
990
1450
  "type": "object",
991
1451
  "properties": {
992
1452
  "symbol": {"type": "string", "description": "Stock ticker symbol"},
993
- "period": {"type": "string", "description": "Time period: 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, max", "default": "3mo"}
1453
+ "period": {"type": "string", "description": "Time period: 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, max", "default": "3mo"},
1454
+ "interval": {"type": "string", "description": "Data interval: 1m, 2m, 5m, 15m, 30m, 60m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo", "default": "1d"}
994
1455
  },
995
1456
  "required": ["symbol"]
996
1457
  }
@@ -1274,6 +1735,127 @@ TOOLS = [
1274
1735
  }
1275
1736
  }
1276
1737
  },
1738
+ # Chart generation tools
1739
+ {
1740
+ "type": "function",
1741
+ "function": {
1742
+ "name": "generate_stock_chart",
1743
+ "description": "Generate a stock price chart (candlestick, line, or technical) with optional indicators. Returns file path where chart is saved.",
1744
+ "parameters": {
1745
+ "type": "object",
1746
+ "properties": {
1747
+ "symbol": {"type": "string", "description": "Stock ticker symbol (e.g., AAPL, NVDA)"},
1748
+ "period": {"type": "string", "description": "Time period: 1mo, 3mo, 6mo, 1y, 2y, 5y", "default": "6mo"},
1749
+ "chart_type": {"type": "string", "enum": ["candlestick", "line", "technical"], "description": "Type of chart", "default": "candlestick"},
1750
+ "show_volume": {"type": "boolean", "description": "Show volume bars", "default": True},
1751
+ "show_indicators": {"type": "boolean", "description": "Show moving averages/indicators", "default": True}
1752
+ },
1753
+ "required": ["symbol"]
1754
+ }
1755
+ }
1756
+ },
1757
+ {
1758
+ "type": "function",
1759
+ "function": {
1760
+ "name": "generate_comparison_chart",
1761
+ "description": "Generate a comparison chart showing multiple stocks' performance over time",
1762
+ "parameters": {
1763
+ "type": "object",
1764
+ "properties": {
1765
+ "symbols": {"type": "array", "items": {"type": "string"}, "description": "List of stock symbols to compare"},
1766
+ "period": {"type": "string", "description": "Time period for comparison", "default": "1y"},
1767
+ "normalize": {"type": "boolean", "description": "Normalize to percentage returns", "default": True}
1768
+ },
1769
+ "required": ["symbols"]
1770
+ }
1771
+ }
1772
+ },
1773
+ # Advanced analysis tools
1774
+ {
1775
+ "type": "function",
1776
+ "function": {
1777
+ "name": "get_valuation_metrics",
1778
+ "description": "Get comprehensive valuation metrics (P/E, P/B, PEG, EV/EBITDA) with assessment",
1779
+ "parameters": {
1780
+ "type": "object",
1781
+ "properties": {
1782
+ "symbol": {"type": "string", "description": "Stock ticker symbol"}
1783
+ },
1784
+ "required": ["symbol"]
1785
+ }
1786
+ }
1787
+ },
1788
+ {
1789
+ "type": "function",
1790
+ "function": {
1791
+ "name": "get_risk_metrics",
1792
+ "description": "Calculate risk metrics: volatility, VaR, max drawdown, Sharpe, Sortino, Beta, Alpha",
1793
+ "parameters": {
1794
+ "type": "object",
1795
+ "properties": {
1796
+ "symbol": {"type": "string", "description": "Stock ticker symbol"},
1797
+ "period": {"type": "string", "description": "Analysis period (1y, 2y, 5y)", "default": "1y"}
1798
+ },
1799
+ "required": ["symbol"]
1800
+ }
1801
+ }
1802
+ },
1803
+ {
1804
+ "type": "function",
1805
+ "function": {
1806
+ "name": "get_earnings_analysis",
1807
+ "description": "Get earnings analysis: EPS, upcoming dates, quarterly history, growth",
1808
+ "parameters": {
1809
+ "type": "object",
1810
+ "properties": {
1811
+ "symbol": {"type": "string", "description": "Stock ticker symbol"}
1812
+ },
1813
+ "required": ["symbol"]
1814
+ }
1815
+ }
1816
+ },
1817
+ {
1818
+ "type": "function",
1819
+ "function": {
1820
+ "name": "get_dividend_analysis",
1821
+ "description": "Get dividend analysis: yield, payout ratio, ex-date, dividend history and growth",
1822
+ "parameters": {
1823
+ "type": "object",
1824
+ "properties": {
1825
+ "symbol": {"type": "string", "description": "Stock ticker symbol"}
1826
+ },
1827
+ "required": ["symbol"]
1828
+ }
1829
+ }
1830
+ },
1831
+ {
1832
+ "type": "function",
1833
+ "function": {
1834
+ "name": "get_options_summary",
1835
+ "description": "Get options chain summary: put/call ratio, implied volatility, ATM options",
1836
+ "parameters": {
1837
+ "type": "object",
1838
+ "properties": {
1839
+ "symbol": {"type": "string", "description": "Stock ticker symbol"}
1840
+ },
1841
+ "required": ["symbol"]
1842
+ }
1843
+ }
1844
+ },
1845
+ {
1846
+ "type": "function",
1847
+ "function": {
1848
+ "name": "get_peer_comparison",
1849
+ "description": "Compare a stock with its industry peers on key metrics",
1850
+ "parameters": {
1851
+ "type": "object",
1852
+ "properties": {
1853
+ "symbol": {"type": "string", "description": "Stock ticker symbol"}
1854
+ },
1855
+ "required": ["symbol"]
1856
+ }
1857
+ }
1858
+ },
1277
1859
  ]
1278
1860
 
1279
1861
 
@@ -1303,6 +1885,16 @@ TOOL_FUNCTIONS = {
1303
1885
  "polygon_get_aggregates": polygon_get_aggregates,
1304
1886
  "polygon_get_ticker_news": polygon_get_ticker_news,
1305
1887
  "polygon_market_status": polygon_market_status,
1888
+ # Chart generation
1889
+ "generate_stock_chart": generate_stock_chart,
1890
+ "generate_comparison_chart": generate_comparison_chart,
1891
+ # Advanced analysis
1892
+ "get_valuation_metrics": get_valuation_metrics,
1893
+ "get_risk_metrics": get_risk_metrics,
1894
+ "get_earnings_analysis": get_earnings_analysis,
1895
+ "get_dividend_analysis": get_dividend_analysis,
1896
+ "get_options_summary": get_options_summary,
1897
+ "get_peer_comparison": get_peer_comparison,
1306
1898
  }
1307
1899
 
1308
1900