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/__init__.py +182 -6
- sigma/__main__.py +2 -2
- sigma/analytics/__init__.py +636 -0
- sigma/app.py +563 -898
- sigma/backtest.py +372 -0
- sigma/charts.py +407 -0
- sigma/cli.py +434 -0
- sigma/comparison.py +611 -0
- sigma/config.py +195 -0
- sigma/core/__init__.py +4 -17
- sigma/core/engine.py +493 -0
- sigma/core/intent.py +595 -0
- sigma/core/models.py +516 -125
- sigma/data/__init__.py +681 -0
- sigma/data/models.py +130 -0
- sigma/llm.py +401 -0
- sigma/monitoring.py +666 -0
- sigma/portfolio.py +697 -0
- sigma/reporting.py +658 -0
- sigma/robustness.py +675 -0
- sigma/setup.py +305 -402
- sigma/strategy.py +753 -0
- sigma/tools/backtest.py +23 -5
- sigma/tools.py +617 -0
- sigma/visualization.py +766 -0
- sigma_terminal-3.2.0.dist-info/METADATA +298 -0
- sigma_terminal-3.2.0.dist-info/RECORD +30 -0
- sigma_terminal-3.2.0.dist-info/entry_points.txt +6 -0
- sigma_terminal-3.2.0.dist-info/licenses/LICENSE +25 -0
- sigma/core/agent.py +0 -205
- sigma/core/config.py +0 -119
- sigma/core/llm.py +0 -794
- sigma/tools/__init__.py +0 -5
- sigma/tools/charts.py +0 -400
- sigma/tools/financial.py +0 -1457
- sigma/ui/__init__.py +0 -1
- sigma_terminal-2.0.1.dist-info/METADATA +0 -222
- sigma_terminal-2.0.1.dist-info/RECORD +0 -19
- sigma_terminal-2.0.1.dist-info/entry_points.txt +0 -2
- sigma_terminal-2.0.1.dist-info/licenses/LICENSE +0 -42
- {sigma_terminal-2.0.1.dist-info → sigma_terminal-3.2.0.dist-info}/WHEEL +0 -0
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
|
|
910
|
+
# Provide QuantConnect instructions for advanced backtest
|
|
911
911
|
result["quantconnect_instructions"] = [
|
|
912
|
-
"For
|
|
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
|
|
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
|
|
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}"}
|