Update
This commit is contained in:
parent
ea4ee9176b
commit
2376fc74a1
|
|
@ -50,7 +50,7 @@ Analyze {ticker}'s technical setup and identify the 3-5 most relevant trading si
|
||||||
1. **Call get_stock_data first** to understand recent price action (request only last 6 months)
|
1. **Call get_stock_data first** to understand recent price action (request only last 6 months)
|
||||||
2. **Identify current market regime** (trending up/down/sideways/breakout setup)
|
2. **Identify current market regime** (trending up/down/sideways/breakout setup)
|
||||||
3. **Select 4-6 complementary indicators** based on regime
|
3. **Select 4-6 complementary indicators** based on regime
|
||||||
4. **Call get_indicators SEPARATELY for EACH** (e.g., first call with indicator="rsi", then indicator="macd")
|
4. **Call get_indicators ONCE** to get a comprehensive technical report (includes RSI, MACD, Moving Averages, Bollinger Bands, ATR, etc.)
|
||||||
5. **Synthesize findings** into specific trading signals
|
5. **Synthesize findings** into specific trading signals
|
||||||
|
|
||||||
## OUTPUT STRUCTURE (MANDATORY)
|
## OUTPUT STRUCTURE (MANDATORY)
|
||||||
|
|
@ -85,8 +85,8 @@ For each signal:
|
||||||
| 50 SMA | $145 | Support | Trend intact if held | Ongoing |
|
| 50 SMA | $145 | Support | Trend intact if held | Ongoing |
|
||||||
|
|
||||||
## CRITICAL RULES
|
## CRITICAL RULES
|
||||||
- ❌ DO NOT pass multiple indicators in one call: `indicator="rsi,macd"`
|
- ❌ DO NOT try to pass specific indicators: `indicator="rsi"` (the tool gives you everything at once)
|
||||||
- ✅ DO call get_indicators separately: `indicator="rsi"` then `indicator="macd"`
|
- ✅ DO call `get_indicators(symbol=ticker, curr_date=current_date)` once to get all data
|
||||||
- ❌ DO NOT say "trends are mixed" without specific examples
|
- ❌ DO NOT say "trends are mixed" without specific examples
|
||||||
- ✅ DO provide concrete signals with specific price levels and timeframes
|
- ✅ DO provide concrete signals with specific price levels and timeframes
|
||||||
- ❌ DO NOT select redundant indicators (e.g., both close_50_sma and close_200_sma)
|
- ❌ DO NOT select redundant indicators (e.g., both close_50_sma and close_200_sma)
|
||||||
|
|
|
||||||
|
|
@ -686,7 +686,7 @@ Lesson: {lesson_text}
|
||||||
try:
|
try:
|
||||||
# Get technical/price data (what Market Analyst sees)
|
# Get technical/price data (what Market Analyst sees)
|
||||||
stock_data = execute_tool("get_stock_data", symbol=ticker, start_date=date)
|
stock_data = execute_tool("get_stock_data", symbol=ticker, start_date=date)
|
||||||
indicators = execute_tool("get_indicators", symbol=ticker, start_date=date)
|
indicators = execute_tool("get_indicators", symbol=ticker, curr_date=date)
|
||||||
data["market_report"] = f"Stock Data:\n{stock_data}\n\nTechnical Indicators:\n{indicators}"
|
data["market_report"] = f"Stock Data:\n{stock_data}\n\nTechnical Indicators:\n{indicators}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
data["market_report"] = f"Error fetching market data: {e}"
|
data["market_report"] = f"Error fetching market data: {e}"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from typing import Annotated
|
||||||
|
|
||||||
# Import from vendor-specific modules
|
# Import from vendor-specific modules
|
||||||
from .local import get_YFin_data, get_finnhub_news, get_finnhub_company_insider_sentiment, get_finnhub_company_insider_transactions, get_simfin_balance_sheet, get_simfin_cashflow, get_simfin_income_statements, get_reddit_global_news, get_reddit_company_news
|
from .local import get_YFin_data, get_finnhub_news, get_finnhub_company_insider_sentiment, get_finnhub_company_insider_transactions, get_simfin_balance_sheet, get_simfin_cashflow, get_simfin_income_statements, get_reddit_global_news, get_reddit_company_news
|
||||||
from .y_finance import get_YFin_data_online, get_stock_stats_indicators_window, get_balance_sheet as get_yfinance_balance_sheet, get_cashflow as get_yfinance_cashflow, get_income_statement as get_yfinance_income_statement, get_insider_transactions as get_yfinance_insider_transactions, validate_ticker as validate_ticker_yfinance
|
from .y_finance import get_YFin_data_online, get_stock_stats_indicators_window, get_technical_analysis, get_balance_sheet as get_yfinance_balance_sheet, get_cashflow as get_yfinance_cashflow, get_income_statement as get_yfinance_income_statement, get_insider_transactions as get_yfinance_insider_transactions, validate_ticker as validate_ticker_yfinance
|
||||||
from .google import get_google_news, get_global_news_google
|
from .google import get_google_news, get_global_news_google
|
||||||
from .openai import get_stock_news_openai, get_global_news_openai, get_fundamentals_openai
|
from .openai import get_stock_news_openai, get_global_news_openai, get_fundamentals_openai
|
||||||
from .alpha_vantage import (
|
from .alpha_vantage import (
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,415 @@ def get_stockstats_indicator(
|
||||||
return str(indicator_value)
|
return str(indicator_value)
|
||||||
|
|
||||||
|
|
||||||
|
def get_technical_analysis(
|
||||||
|
symbol: Annotated[str, "ticker symbol of the company"],
|
||||||
|
curr_date: Annotated[str, "The current trading date, YYYY-mm-dd"],
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Get a concise technical analysis summary with key indicators, signals, and trend interpretation.
|
||||||
|
|
||||||
|
Returns analysis-ready output instead of verbose day-by-day data.
|
||||||
|
"""
|
||||||
|
from .config import get_config
|
||||||
|
from stockstats import wrap
|
||||||
|
|
||||||
|
# Default indicators to analyze
|
||||||
|
indicators = ["rsi", "stoch", "macd", "adx", "close_20_ema", "close_50_sma", "close_200_sma", "boll", "atr", "obv", "vwap", "fib"]
|
||||||
|
|
||||||
|
# Fetch price data (last 60 days for indicator calculation)
|
||||||
|
curr_date_dt = pd.to_datetime(curr_date)
|
||||||
|
start_date = curr_date_dt - pd.DateOffset(days=200) # Need enough history for 200 SMA
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = yf.download(
|
||||||
|
symbol,
|
||||||
|
start=start_date.strftime("%Y-%m-%d"),
|
||||||
|
end=curr_date_dt.strftime("%Y-%m-%d"),
|
||||||
|
multi_level_index=False,
|
||||||
|
progress=False,
|
||||||
|
auto_adjust=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if data.empty:
|
||||||
|
return f"No data found for {symbol}"
|
||||||
|
|
||||||
|
data = data.reset_index()
|
||||||
|
df = wrap(data)
|
||||||
|
|
||||||
|
# Get latest values
|
||||||
|
latest = df.iloc[-1]
|
||||||
|
prev = df.iloc[-2] if len(df) > 1 else latest
|
||||||
|
prev_5 = df.iloc[-5] if len(df) > 5 else latest
|
||||||
|
|
||||||
|
current_price = float(latest['close'])
|
||||||
|
|
||||||
|
# Build analysis
|
||||||
|
analysis = []
|
||||||
|
analysis.append(f"# Technical Analysis for {symbol.upper()}")
|
||||||
|
analysis.append(f"**Date:** {curr_date}")
|
||||||
|
analysis.append(f"**Current Price:** ${current_price:.2f}")
|
||||||
|
analysis.append("")
|
||||||
|
|
||||||
|
# Price action summary
|
||||||
|
daily_change = ((current_price - float(prev['close'])) / float(prev['close'])) * 100
|
||||||
|
weekly_change = ((current_price - float(prev_5['close'])) / float(prev_5['close'])) * 100
|
||||||
|
analysis.append(f"## Price Action")
|
||||||
|
analysis.append(f"- **Daily Change:** {daily_change:+.2f}%")
|
||||||
|
analysis.append(f"- **5-Day Change:** {weekly_change:+.2f}%")
|
||||||
|
analysis.append("")
|
||||||
|
|
||||||
|
# RSI Analysis
|
||||||
|
if 'rsi' in indicators:
|
||||||
|
try:
|
||||||
|
df['rsi'] # Trigger calculation
|
||||||
|
rsi = float(df.iloc[-1]['rsi'])
|
||||||
|
rsi_prev = float(df.iloc[-5]['rsi']) if len(df) > 5 else rsi
|
||||||
|
|
||||||
|
if rsi > 70:
|
||||||
|
rsi_signal = "OVERBOUGHT ⚠️"
|
||||||
|
elif rsi < 30:
|
||||||
|
rsi_signal = "OVERSOLD ⚡"
|
||||||
|
elif rsi > 50:
|
||||||
|
rsi_signal = "Bullish"
|
||||||
|
else:
|
||||||
|
rsi_signal = "Bearish"
|
||||||
|
|
||||||
|
rsi_trend = "↑" if rsi > rsi_prev else "↓"
|
||||||
|
analysis.append(f"## RSI (14)")
|
||||||
|
analysis.append(f"- **Value:** {rsi:.1f} {rsi_trend}")
|
||||||
|
analysis.append(f"- **Signal:** {rsi_signal}")
|
||||||
|
analysis.append("")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# MACD Analysis
|
||||||
|
if 'macd' in indicators:
|
||||||
|
try:
|
||||||
|
df['macd']
|
||||||
|
df['macds']
|
||||||
|
df['macdh']
|
||||||
|
macd = float(df.iloc[-1]['macd'])
|
||||||
|
signal = float(df.iloc[-1]['macds'])
|
||||||
|
histogram = float(df.iloc[-1]['macdh'])
|
||||||
|
hist_prev = float(df.iloc[-2]['macdh']) if len(df) > 1 else histogram
|
||||||
|
|
||||||
|
if macd > signal and histogram > 0:
|
||||||
|
macd_signal = "BULLISH CROSSOVER ⚡" if histogram > hist_prev else "Bullish"
|
||||||
|
elif macd < signal and histogram < 0:
|
||||||
|
macd_signal = "BEARISH CROSSOVER ⚠️" if histogram < hist_prev else "Bearish"
|
||||||
|
else:
|
||||||
|
macd_signal = "Neutral"
|
||||||
|
|
||||||
|
momentum = "Strengthening ↑" if abs(histogram) > abs(hist_prev) else "Weakening ↓"
|
||||||
|
analysis.append(f"## MACD")
|
||||||
|
analysis.append(f"- **MACD Line:** {macd:.3f}")
|
||||||
|
analysis.append(f"- **Signal Line:** {signal:.3f}")
|
||||||
|
analysis.append(f"- **Histogram:** {histogram:.3f} ({momentum})")
|
||||||
|
analysis.append(f"- **Signal:** {macd_signal}")
|
||||||
|
analysis.append("")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Moving Averages
|
||||||
|
if 'close_50_sma' in indicators or 'close_200_sma' in indicators:
|
||||||
|
try:
|
||||||
|
df['close_50_sma']
|
||||||
|
df['close_200_sma']
|
||||||
|
sma_50 = float(df.iloc[-1]['close_50_sma'])
|
||||||
|
sma_200 = float(df.iloc[-1]['close_200_sma'])
|
||||||
|
|
||||||
|
# Trend determination
|
||||||
|
if current_price > sma_50 > sma_200:
|
||||||
|
trend = "STRONG UPTREND ⚡"
|
||||||
|
elif current_price > sma_50:
|
||||||
|
trend = "Uptrend"
|
||||||
|
elif current_price < sma_50 < sma_200:
|
||||||
|
trend = "STRONG DOWNTREND ⚠️"
|
||||||
|
elif current_price < sma_50:
|
||||||
|
trend = "Downtrend"
|
||||||
|
else:
|
||||||
|
trend = "Sideways"
|
||||||
|
|
||||||
|
# Golden/Death cross detection
|
||||||
|
sma_50_prev = float(df.iloc[-5]['close_50_sma']) if len(df) > 5 else sma_50
|
||||||
|
sma_200_prev = float(df.iloc[-5]['close_200_sma']) if len(df) > 5 else sma_200
|
||||||
|
|
||||||
|
cross = ""
|
||||||
|
if sma_50 > sma_200 and sma_50_prev < sma_200_prev:
|
||||||
|
cross = " (GOLDEN CROSS ⚡)"
|
||||||
|
elif sma_50 < sma_200 and sma_50_prev > sma_200_prev:
|
||||||
|
cross = " (DEATH CROSS ⚠️)"
|
||||||
|
|
||||||
|
analysis.append(f"## Moving Averages")
|
||||||
|
analysis.append(f"- **50 SMA:** ${sma_50:.2f} ({'+' if current_price > sma_50 else ''}{((current_price - sma_50) / sma_50 * 100):.1f}% from price)")
|
||||||
|
analysis.append(f"- **200 SMA:** ${sma_200:.2f} ({'+' if current_price > sma_200 else ''}{((current_price - sma_200) / sma_200 * 100):.1f}% from price)")
|
||||||
|
analysis.append(f"- **Trend:** {trend}{cross}")
|
||||||
|
analysis.append("")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Bollinger Bands
|
||||||
|
if 'boll' in indicators:
|
||||||
|
try:
|
||||||
|
df['boll']
|
||||||
|
df['boll_ub']
|
||||||
|
df['boll_lb']
|
||||||
|
middle = float(df.iloc[-1]['boll'])
|
||||||
|
upper = float(df.iloc[-1]['boll_ub'])
|
||||||
|
lower = float(df.iloc[-1]['boll_lb'])
|
||||||
|
|
||||||
|
# Position within bands (0 = lower, 1 = upper)
|
||||||
|
band_position = (current_price - lower) / (upper - lower) if upper != lower else 0.5
|
||||||
|
|
||||||
|
if band_position > 0.95:
|
||||||
|
bb_signal = "AT UPPER BAND - Potential reversal ⚠️"
|
||||||
|
elif band_position < 0.05:
|
||||||
|
bb_signal = "AT LOWER BAND - Potential bounce ⚡"
|
||||||
|
elif band_position > 0.8:
|
||||||
|
bb_signal = "Near upper band"
|
||||||
|
elif band_position < 0.2:
|
||||||
|
bb_signal = "Near lower band"
|
||||||
|
else:
|
||||||
|
bb_signal = "Within bands"
|
||||||
|
|
||||||
|
bandwidth = ((upper - lower) / middle) * 100
|
||||||
|
analysis.append(f"## Bollinger Bands (20,2)")
|
||||||
|
analysis.append(f"- **Upper:** ${upper:.2f}")
|
||||||
|
analysis.append(f"- **Middle:** ${middle:.2f}")
|
||||||
|
analysis.append(f"- **Lower:** ${lower:.2f}")
|
||||||
|
analysis.append(f"- **Band Position:** {band_position:.0%}")
|
||||||
|
analysis.append(f"- **Bandwidth:** {bandwidth:.1f}% (volatility indicator)")
|
||||||
|
analysis.append(f"- **Signal:** {bb_signal}")
|
||||||
|
analysis.append("")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ATR (Volatility)
|
||||||
|
if 'atr' in indicators:
|
||||||
|
try:
|
||||||
|
df['atr']
|
||||||
|
atr = float(df.iloc[-1]['atr'])
|
||||||
|
atr_pct = (atr / current_price) * 100
|
||||||
|
|
||||||
|
if atr_pct > 5:
|
||||||
|
vol_level = "HIGH VOLATILITY ⚠️"
|
||||||
|
elif atr_pct > 2:
|
||||||
|
vol_level = "Moderate volatility"
|
||||||
|
else:
|
||||||
|
vol_level = "Low volatility"
|
||||||
|
|
||||||
|
analysis.append(f"## ATR (Volatility)")
|
||||||
|
analysis.append(f"- **ATR:** ${atr:.2f} ({atr_pct:.1f}% of price)")
|
||||||
|
analysis.append(f"- **Level:** {vol_level}")
|
||||||
|
analysis.append(f"- **Suggested Stop-Loss:** ${current_price - (1.5 * atr):.2f} (1.5x ATR)")
|
||||||
|
analysis.append("")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Stochastic Oscillator
|
||||||
|
if 'stoch' in indicators:
|
||||||
|
try:
|
||||||
|
df['kdjk'] # Stochastic %K
|
||||||
|
df['kdjd'] # Stochastic %D
|
||||||
|
stoch_k = float(df.iloc[-1]['kdjk'])
|
||||||
|
stoch_d = float(df.iloc[-1]['kdjd'])
|
||||||
|
stoch_k_prev = float(df.iloc[-2]['kdjk']) if len(df) > 1 else stoch_k
|
||||||
|
|
||||||
|
if stoch_k > 80 and stoch_d > 80:
|
||||||
|
stoch_signal = "OVERBOUGHT ⚠️"
|
||||||
|
elif stoch_k < 20 and stoch_d < 20:
|
||||||
|
stoch_signal = "OVERSOLD ⚡"
|
||||||
|
elif stoch_k > stoch_d and stoch_k_prev < stoch_d:
|
||||||
|
stoch_signal = "Bullish crossover ⚡"
|
||||||
|
elif stoch_k < stoch_d and stoch_k_prev > stoch_d:
|
||||||
|
stoch_signal = "Bearish crossover ⚠️"
|
||||||
|
elif stoch_k > 50:
|
||||||
|
stoch_signal = "Bullish"
|
||||||
|
else:
|
||||||
|
stoch_signal = "Bearish"
|
||||||
|
|
||||||
|
analysis.append(f"## Stochastic (14,3,3)")
|
||||||
|
analysis.append(f"- **%K:** {stoch_k:.1f}")
|
||||||
|
analysis.append(f"- **%D:** {stoch_d:.1f}")
|
||||||
|
analysis.append(f"- **Signal:** {stoch_signal}")
|
||||||
|
analysis.append("")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ADX (Trend Strength)
|
||||||
|
if 'adx' in indicators:
|
||||||
|
try:
|
||||||
|
df['adx']
|
||||||
|
df['dx']
|
||||||
|
adx = float(df.iloc[-1]['adx'])
|
||||||
|
adx_prev = float(df.iloc[-5]['adx']) if len(df) > 5 else adx
|
||||||
|
|
||||||
|
if adx > 50:
|
||||||
|
trend_strength = "VERY STRONG TREND ⚡"
|
||||||
|
elif adx > 25:
|
||||||
|
trend_strength = "Strong trend"
|
||||||
|
elif adx > 20:
|
||||||
|
trend_strength = "Trending"
|
||||||
|
else:
|
||||||
|
trend_strength = "WEAK/NO TREND (range-bound) ⚠️"
|
||||||
|
|
||||||
|
adx_direction = "Strengthening ↑" if adx > adx_prev else "Weakening ↓"
|
||||||
|
analysis.append(f"## ADX (Trend Strength)")
|
||||||
|
analysis.append(f"- **ADX:** {adx:.1f} ({adx_direction})")
|
||||||
|
analysis.append(f"- **Interpretation:** {trend_strength}")
|
||||||
|
analysis.append("")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 20 EMA (Short-term trend)
|
||||||
|
if 'close_20_ema' in indicators:
|
||||||
|
try:
|
||||||
|
df['close_20_ema']
|
||||||
|
ema_20 = float(df.iloc[-1]['close_20_ema'])
|
||||||
|
|
||||||
|
pct_from_ema = ((current_price - ema_20) / ema_20) * 100
|
||||||
|
if current_price > ema_20:
|
||||||
|
ema_signal = "Price ABOVE 20 EMA (short-term bullish)"
|
||||||
|
else:
|
||||||
|
ema_signal = "Price BELOW 20 EMA (short-term bearish)"
|
||||||
|
|
||||||
|
analysis.append(f"## 20 EMA")
|
||||||
|
analysis.append(f"- **Value:** ${ema_20:.2f} ({pct_from_ema:+.1f}% from price)")
|
||||||
|
analysis.append(f"- **Signal:** {ema_signal}")
|
||||||
|
analysis.append("")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# OBV (On-Balance Volume)
|
||||||
|
if 'obv' in indicators:
|
||||||
|
try:
|
||||||
|
# Calculate OBV manually since stockstats may not have it
|
||||||
|
obv = 0
|
||||||
|
obv_values = [0]
|
||||||
|
for i in range(1, len(df)):
|
||||||
|
if float(df.iloc[i]['close']) > float(df.iloc[i-1]['close']):
|
||||||
|
obv += float(df.iloc[i]['volume'])
|
||||||
|
elif float(df.iloc[i]['close']) < float(df.iloc[i-1]['close']):
|
||||||
|
obv -= float(df.iloc[i]['volume'])
|
||||||
|
obv_values.append(obv)
|
||||||
|
|
||||||
|
current_obv = obv_values[-1]
|
||||||
|
obv_5_ago = obv_values[-5] if len(obv_values) > 5 else obv_values[0]
|
||||||
|
|
||||||
|
if current_obv > obv_5_ago and current_price > float(df.iloc[-5]['close']):
|
||||||
|
obv_signal = "Confirmed uptrend (price & volume rising)"
|
||||||
|
elif current_obv < obv_5_ago and current_price < float(df.iloc[-5]['close']):
|
||||||
|
obv_signal = "Confirmed downtrend (price & volume falling)"
|
||||||
|
elif current_obv > obv_5_ago and current_price < float(df.iloc[-5]['close']):
|
||||||
|
obv_signal = "BULLISH DIVERGENCE ⚡ (accumulation)"
|
||||||
|
elif current_obv < obv_5_ago and current_price > float(df.iloc[-5]['close']):
|
||||||
|
obv_signal = "BEARISH DIVERGENCE ⚠️ (distribution)"
|
||||||
|
else:
|
||||||
|
obv_signal = "Neutral"
|
||||||
|
|
||||||
|
obv_formatted = f"{current_obv/1e6:.1f}M" if abs(current_obv) > 1e6 else f"{current_obv/1e3:.1f}K"
|
||||||
|
analysis.append(f"## OBV (On-Balance Volume)")
|
||||||
|
analysis.append(f"- **Value:** {obv_formatted}")
|
||||||
|
analysis.append(f"- **5-Day Trend:** {'Rising ↑' if current_obv > obv_5_ago else 'Falling ↓'}")
|
||||||
|
analysis.append(f"- **Signal:** {obv_signal}")
|
||||||
|
analysis.append("")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# VWAP (Volume Weighted Average Price)
|
||||||
|
if 'vwap' in indicators:
|
||||||
|
try:
|
||||||
|
# Calculate VWAP for today (simplified - using recent data)
|
||||||
|
typical_price = (float(df.iloc[-1]['high']) + float(df.iloc[-1]['low']) + float(df.iloc[-1]['close'])) / 3
|
||||||
|
|
||||||
|
# Calculate cumulative VWAP (last 20 periods approximation)
|
||||||
|
recent_df = df.tail(20)
|
||||||
|
tp_vol = ((recent_df['high'] + recent_df['low'] + recent_df['close']) / 3) * recent_df['volume']
|
||||||
|
vwap = float(tp_vol.sum() / recent_df['volume'].sum())
|
||||||
|
|
||||||
|
pct_from_vwap = ((current_price - vwap) / vwap) * 100
|
||||||
|
if current_price > vwap:
|
||||||
|
vwap_signal = "Price ABOVE VWAP (institutional buying)"
|
||||||
|
else:
|
||||||
|
vwap_signal = "Price BELOW VWAP (institutional selling)"
|
||||||
|
|
||||||
|
analysis.append(f"## VWAP (20-period)")
|
||||||
|
analysis.append(f"- **VWAP:** ${vwap:.2f}")
|
||||||
|
analysis.append(f"- **Current vs VWAP:** {pct_from_vwap:+.1f}%")
|
||||||
|
analysis.append(f"- **Signal:** {vwap_signal}")
|
||||||
|
analysis.append("")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Fibonacci Retracement Levels
|
||||||
|
if 'fib' in indicators:
|
||||||
|
try:
|
||||||
|
# Get high and low from last 50 periods
|
||||||
|
recent_high = float(df.tail(50)['high'].max())
|
||||||
|
recent_low = float(df.tail(50)['low'].min())
|
||||||
|
diff = recent_high - recent_low
|
||||||
|
|
||||||
|
fib_levels = {
|
||||||
|
"0.0% (High)": recent_high,
|
||||||
|
"23.6%": recent_high - (diff * 0.236),
|
||||||
|
"38.2%": recent_high - (diff * 0.382),
|
||||||
|
"50.0%": recent_high - (diff * 0.5),
|
||||||
|
"61.8%": recent_high - (diff * 0.618),
|
||||||
|
"78.6%": recent_high - (diff * 0.786),
|
||||||
|
"100% (Low)": recent_low,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find nearest support and resistance
|
||||||
|
support = None
|
||||||
|
resistance = None
|
||||||
|
for level_name, level_price in fib_levels.items():
|
||||||
|
if level_price < current_price and (support is None or level_price > support[1]):
|
||||||
|
support = (level_name, level_price)
|
||||||
|
if level_price > current_price and (resistance is None or level_price < resistance[1]):
|
||||||
|
resistance = (level_name, level_price)
|
||||||
|
|
||||||
|
analysis.append(f"## Fibonacci Levels (50-period)")
|
||||||
|
analysis.append(f"- **Recent High:** ${recent_high:.2f}")
|
||||||
|
analysis.append(f"- **Recent Low:** ${recent_low:.2f}")
|
||||||
|
if resistance:
|
||||||
|
analysis.append(f"- **Next Resistance:** ${resistance[1]:.2f} ({resistance[0]})")
|
||||||
|
if support:
|
||||||
|
analysis.append(f"- **Next Support:** ${support[1]:.2f} ({support[0]})")
|
||||||
|
analysis.append("")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Overall Summary
|
||||||
|
analysis.append("## Summary")
|
||||||
|
signals = []
|
||||||
|
|
||||||
|
# Collect all signals for summary
|
||||||
|
try:
|
||||||
|
rsi = float(df.iloc[-1]['rsi'])
|
||||||
|
if rsi > 70:
|
||||||
|
signals.append("RSI overbought")
|
||||||
|
elif rsi < 30:
|
||||||
|
signals.append("RSI oversold")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
if current_price > float(df.iloc[-1]['close_50_sma']):
|
||||||
|
signals.append("Above 50 SMA")
|
||||||
|
else:
|
||||||
|
signals.append("Below 50 SMA")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if signals:
|
||||||
|
analysis.append(f"- **Key Signals:** {', '.join(signals)}")
|
||||||
|
|
||||||
|
return "\n".join(analysis)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return f"Error analyzing {symbol}: {str(e)}"
|
||||||
|
|
||||||
|
|
||||||
def get_balance_sheet(
|
def get_balance_sheet(
|
||||||
ticker: Annotated[str, "ticker symbol of the company"],
|
ticker: Annotated[str, "ticker symbol of the company"],
|
||||||
freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly",
|
freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly",
|
||||||
|
|
@ -388,7 +797,7 @@ def get_insider_transactions(
|
||||||
ticker: Annotated[str, "ticker symbol of the company"],
|
ticker: Annotated[str, "ticker symbol of the company"],
|
||||||
curr_date: Annotated[str, "current date (not used for yfinance)"] = None
|
curr_date: Annotated[str, "current date (not used for yfinance)"] = None
|
||||||
):
|
):
|
||||||
"""Get insider transactions data from yfinance."""
|
"""Get insider transactions data from yfinance with parsed transaction types."""
|
||||||
try:
|
try:
|
||||||
ticker_obj = yf.Ticker(ticker.upper())
|
ticker_obj = yf.Ticker(ticker.upper())
|
||||||
data = ticker_obj.insider_transactions
|
data = ticker_obj.insider_transactions
|
||||||
|
|
@ -396,12 +805,65 @@ def get_insider_transactions(
|
||||||
if data is None or data.empty:
|
if data is None or data.empty:
|
||||||
return f"No insider transactions data found for symbol '{ticker}'"
|
return f"No insider transactions data found for symbol '{ticker}'"
|
||||||
|
|
||||||
# Convert to CSV string for consistency with other functions
|
# Parse the Text column to populate Transaction type
|
||||||
csv_string = data.to_csv()
|
def classify_transaction(text):
|
||||||
|
if pd.isna(text) or text == '':
|
||||||
|
return 'Unknown'
|
||||||
|
text_lower = str(text).lower()
|
||||||
|
if 'sale' in text_lower:
|
||||||
|
return 'Sale'
|
||||||
|
elif 'purchase' in text_lower or 'buy' in text_lower:
|
||||||
|
return 'Purchase'
|
||||||
|
elif 'gift' in text_lower:
|
||||||
|
return 'Gift'
|
||||||
|
elif 'exercise' in text_lower or 'option' in text_lower:
|
||||||
|
return 'Option Exercise'
|
||||||
|
elif 'award' in text_lower or 'grant' in text_lower:
|
||||||
|
return 'Award/Grant'
|
||||||
|
elif 'conversion' in text_lower:
|
||||||
|
return 'Conversion'
|
||||||
|
else:
|
||||||
|
return 'Other'
|
||||||
|
|
||||||
# Add header information
|
# Apply classification
|
||||||
header = f"# Insider Transactions data for {ticker.upper()}\n"
|
data['Transaction'] = data['Text'].apply(classify_transaction)
|
||||||
|
|
||||||
|
# Calculate summary statistics
|
||||||
|
transaction_counts = data['Transaction'].value_counts().to_dict()
|
||||||
|
total_sales_value = data[data['Transaction'] == 'Sale']['Value'].sum()
|
||||||
|
total_purchases_value = data[data['Transaction'] == 'Purchase']['Value'].sum()
|
||||||
|
|
||||||
|
# Determine insider sentiment
|
||||||
|
sales_count = transaction_counts.get('Sale', 0)
|
||||||
|
purchases_count = transaction_counts.get('Purchase', 0)
|
||||||
|
|
||||||
|
if purchases_count > sales_count:
|
||||||
|
sentiment = "BULLISH ⚡ (more buying than selling)"
|
||||||
|
elif sales_count > purchases_count * 2:
|
||||||
|
sentiment = "BEARISH ⚠️ (significant insider selling)"
|
||||||
|
elif sales_count > purchases_count:
|
||||||
|
sentiment = "Slightly bearish (more selling than buying)"
|
||||||
|
else:
|
||||||
|
sentiment = "Neutral"
|
||||||
|
|
||||||
|
# Build summary header
|
||||||
|
header = f"# Insider Transactions for {ticker.upper()}\n"
|
||||||
header += f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
header += f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
||||||
|
header += "## Summary\n"
|
||||||
|
header += f"- **Insider Sentiment:** {sentiment}\n"
|
||||||
|
for tx_type, count in sorted(transaction_counts.items(), key=lambda x: -x[1]):
|
||||||
|
header += f"- **{tx_type}:** {count} transactions\n"
|
||||||
|
if total_sales_value > 0:
|
||||||
|
header += f"- **Total Sales Value:** ${total_sales_value:,.0f}\n"
|
||||||
|
if total_purchases_value > 0:
|
||||||
|
header += f"- **Total Purchases Value:** ${total_purchases_value:,.0f}\n"
|
||||||
|
header += "\n## Transaction Details\n\n"
|
||||||
|
|
||||||
|
# Select key columns for output
|
||||||
|
output_cols = ['Start Date', 'Insider', 'Position', 'Transaction', 'Shares', 'Value', 'Ownership']
|
||||||
|
available_cols = [c for c in output_cols if c in data.columns]
|
||||||
|
|
||||||
|
csv_string = data[available_cols].to_csv(index=False)
|
||||||
|
|
||||||
return header + csv_string
|
return header + csv_string
|
||||||
|
|
||||||
|
|
@ -414,23 +876,16 @@ def validate_ticker(symbol: str) -> bool:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
ticker = yf.Ticker(symbol.upper())
|
ticker = yf.Ticker(symbol.upper())
|
||||||
# Try to fetch 1 day of history
|
# Use fast_info for lighter validation (no historical download needed)
|
||||||
# Suppress yfinance error output
|
# fast_info attributes are lazy-loaded
|
||||||
import sys
|
_ = ticker.fast_info.get("lastPrice")
|
||||||
from io import StringIO
|
return True
|
||||||
|
|
||||||
# Redirect stderr to suppress yfinance error messages
|
|
||||||
original_stderr = sys.stderr
|
|
||||||
sys.stderr = StringIO()
|
|
||||||
|
|
||||||
try:
|
|
||||||
history = ticker.history(period="1d")
|
|
||||||
return not history.empty
|
|
||||||
finally:
|
|
||||||
# Restore stderr
|
|
||||||
sys.stderr = original_stderr
|
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
|
# Fallback to older method if fast_info fails or is missing
|
||||||
|
try:
|
||||||
|
return not ticker.history(period="1d", progress=False).empty
|
||||||
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -202,9 +202,12 @@ Return a JSON object with a 'candidates' array of objects, each having 'ticker'
|
||||||
for c in reddit_candidates:
|
for c in reddit_candidates:
|
||||||
ticker = c.get("ticker", "").upper().strip()
|
ticker = c.get("ticker", "").upper().strip()
|
||||||
context = c.get("context", "Trending on Reddit")
|
context = c.get("context", "Trending on Reddit")
|
||||||
# Validate ticker format (1-5 uppercase letters)
|
# Validate ticker - Exclude garbage, verify existence
|
||||||
if re.match(r'^[A-Z]{1,5}$', ticker):
|
if re.match(r'^[A-Z]{1,5}$', ticker):
|
||||||
candidates.append({"ticker": ticker, "source": "social_trending", "context": context, "sentiment": "unknown"})
|
try:
|
||||||
|
if execute_tool("validate_ticker", symbol=ticker):
|
||||||
|
candidates.append({"ticker": ticker, "source": "social_trending", "context": context})
|
||||||
|
except: pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" Error fetching Reddit tickers: {e}")
|
print(f" Error fetching Reddit tickers: {e}")
|
||||||
|
|
||||||
|
|
@ -247,14 +250,16 @@ Return a JSON object with a 'movers' array containing objects with 'ticker', 'ty
|
||||||
for m in movers:
|
for m in movers:
|
||||||
ticker = m.get('ticker', '').upper().strip()
|
ticker = m.get('ticker', '').upper().strip()
|
||||||
if ticker and re.match(r'^[A-Z]{1,5}$', ticker):
|
if ticker and re.match(r'^[A-Z]{1,5}$', ticker):
|
||||||
|
try:
|
||||||
|
if execute_tool("validate_ticker", symbol=ticker):
|
||||||
mover_type = m.get('type', 'gainer')
|
mover_type = m.get('type', 'gainer')
|
||||||
reason = m.get('reason', f"Top {mover_type}")
|
reason = m.get('reason', f"Top {mover_type}")
|
||||||
candidates.append({
|
candidates.append({
|
||||||
"ticker": ticker,
|
"ticker": ticker,
|
||||||
"source": mover_type,
|
"source": "market_mover",
|
||||||
"context": reason,
|
"context": f"{reason} ({m.get('change_percent', 0)}%)"
|
||||||
"sentiment": "negative" if mover_type == "loser" else "positive"
|
|
||||||
})
|
})
|
||||||
|
except: pass
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" Error fetching Market Movers: {e}")
|
print(f" Error fetching Market Movers: {e}")
|
||||||
|
|
@ -291,7 +296,10 @@ Return a JSON object with a 'candidates' array of objects, each having 'ticker'
|
||||||
ticker = c.get("ticker", "").upper().strip()
|
ticker = c.get("ticker", "").upper().strip()
|
||||||
context = c.get("context", "Upcoming earnings")
|
context = c.get("context", "Upcoming earnings")
|
||||||
if re.match(r'^[A-Z]{1,5}$', ticker):
|
if re.match(r'^[A-Z]{1,5}$', ticker):
|
||||||
candidates.append({"ticker": ticker, "source": "earnings_catalyst", "context": context, "sentiment": "unknown"})
|
try:
|
||||||
|
if execute_tool("validate_ticker", symbol=ticker):
|
||||||
|
candidates.append({"ticker": ticker, "source": "earnings_catalyst", "context": context})
|
||||||
|
except: pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" Error fetching Earnings Calendar: {e}")
|
print(f" Error fetching Earnings Calendar: {e}")
|
||||||
|
|
||||||
|
|
@ -327,7 +335,10 @@ Return a JSON object with a 'candidates' array of objects, each having 'ticker'
|
||||||
ticker = c.get("ticker", "").upper().strip()
|
ticker = c.get("ticker", "").upper().strip()
|
||||||
context = c.get("context", "Recent/upcoming IPO")
|
context = c.get("context", "Recent/upcoming IPO")
|
||||||
if re.match(r'^[A-Z]{1,5}$', ticker):
|
if re.match(r'^[A-Z]{1,5}$', ticker):
|
||||||
candidates.append({"ticker": ticker, "source": "ipo_listing", "context": context, "sentiment": "unknown"})
|
try:
|
||||||
|
if execute_tool("validate_ticker", symbol=ticker):
|
||||||
|
candidates.append({"ticker": ticker, "source": "ipo_listing", "context": context})
|
||||||
|
except: pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" Error fetching IPO Calendar: {e}")
|
print(f" Error fetching IPO Calendar: {e}")
|
||||||
|
|
||||||
|
|
@ -364,12 +375,101 @@ Return a JSON object with a 'candidates' array of objects, each having 'ticker'
|
||||||
ticker = c.get("ticker", "").upper().strip()
|
ticker = c.get("ticker", "").upper().strip()
|
||||||
context = c.get("context", "High short interest")
|
context = c.get("context", "High short interest")
|
||||||
if re.match(r'^[A-Z]{1,5}$', ticker):
|
if re.match(r'^[A-Z]{1,5}$', ticker):
|
||||||
candidates.append({"ticker": ticker, "source": "short_squeeze", "context": context, "sentiment": "unknown"})
|
try:
|
||||||
|
if execute_tool("validate_ticker", symbol=ticker):
|
||||||
|
candidates.append({"ticker": ticker, "source": "short_squeeze", "context": context})
|
||||||
|
except: pass
|
||||||
|
|
||||||
print(f" Found {len(short_candidates)} short squeeze candidates")
|
print(f" Found {len(short_candidates)} short squeeze candidates")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" Error fetching Short Interest: {e}")
|
print(f" Error fetching Short Interest: {e}")
|
||||||
|
|
||||||
|
# 6. Unusual Volume Detection (Accumulation Signal)
|
||||||
|
try:
|
||||||
|
from datetime import datetime
|
||||||
|
today = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
volume_report = execute_tool(
|
||||||
|
"get_unusual_volume",
|
||||||
|
date=today,
|
||||||
|
min_volume_multiple=3.0, # 3x average volume
|
||||||
|
max_price_change=5.0, # Less than 5% price change
|
||||||
|
top_n=15
|
||||||
|
)
|
||||||
|
|
||||||
|
# Extract tickers with volume context
|
||||||
|
prompt = """Extract stock tickers from this unusual volume report with context about the accumulation pattern.
|
||||||
|
|
||||||
|
For each ticker, include:
|
||||||
|
- ticker: The stock symbol (1-5 uppercase letters)
|
||||||
|
- context: Volume multiple, price change, and any interpretation of the pattern
|
||||||
|
|
||||||
|
Unusual Volume Report:
|
||||||
|
{report}
|
||||||
|
|
||||||
|
Return a JSON object with a 'candidates' array of objects, each having 'ticker' and 'context' fields.""".format(report=volume_report)
|
||||||
|
|
||||||
|
structured_llm = self.quick_thinking_llm.with_structured_output(
|
||||||
|
schema=TickerContextList.model_json_schema(),
|
||||||
|
method="json_schema"
|
||||||
|
)
|
||||||
|
response = structured_llm.invoke([HumanMessage(content=prompt)])
|
||||||
|
|
||||||
|
volume_candidates = response.get("candidates", [])
|
||||||
|
for c in volume_candidates:
|
||||||
|
ticker = c.get("ticker", "").upper().strip()
|
||||||
|
context = c.get("context", "Unusual volume pattern")
|
||||||
|
if re.match(r'^[A-Z]{1,5}$', ticker):
|
||||||
|
try:
|
||||||
|
if execute_tool("validate_ticker", symbol=ticker):
|
||||||
|
candidates.append({"ticker": ticker, "source": "unusual_volume", "context": context})
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
print(f" Found {len(volume_candidates)} unusual volume candidates")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Error fetching Unusual Volume: {e}")
|
||||||
|
|
||||||
|
# 7. Analyst Rating Changes (Institutional Catalyst)
|
||||||
|
try:
|
||||||
|
analyst_report = execute_tool(
|
||||||
|
"get_analyst_rating_changes",
|
||||||
|
lookback_days=7,
|
||||||
|
change_types=["upgrade", "initiated"], # Focus on positive catalysts
|
||||||
|
top_n=15
|
||||||
|
)
|
||||||
|
|
||||||
|
# Extract tickers with analyst context
|
||||||
|
prompt = """Extract stock tickers from this analyst rating changes report with context about the rating action.
|
||||||
|
|
||||||
|
For each ticker, include:
|
||||||
|
- ticker: The stock symbol (1-5 uppercase letters)
|
||||||
|
- context: Type of change (upgrade/initiated), analyst firm, price target, and any other relevant details
|
||||||
|
|
||||||
|
Analyst Rating Changes:
|
||||||
|
{report}
|
||||||
|
|
||||||
|
Return a JSON object with a 'candidates' array of objects, each having 'ticker' and 'context' fields.""".format(report=analyst_report)
|
||||||
|
|
||||||
|
structured_llm = self.quick_thinking_llm.with_structured_output(
|
||||||
|
schema=TickerContextList.model_json_schema(),
|
||||||
|
method="json_schema"
|
||||||
|
)
|
||||||
|
response = structured_llm.invoke([HumanMessage(content=prompt)])
|
||||||
|
|
||||||
|
analyst_candidates = response.get("candidates", [])
|
||||||
|
for c in analyst_candidates:
|
||||||
|
ticker = c.get("ticker", "").upper().strip()
|
||||||
|
context = c.get("context", "Recent analyst action")
|
||||||
|
if re.match(r'^[A-Z]{1,5}$', ticker):
|
||||||
|
try:
|
||||||
|
if execute_tool("validate_ticker", symbol=ticker):
|
||||||
|
candidates.append({"ticker": ticker, "source": "analyst_upgrade", "context": context})
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
print(f" Found {len(analyst_candidates)} analyst upgrade candidates")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Error fetching Analyst Ratings: {e}")
|
||||||
|
|
||||||
# Deduplicate
|
# Deduplicate
|
||||||
unique_candidates = {}
|
unique_candidates = {}
|
||||||
for c in candidates:
|
for c in candidates:
|
||||||
|
|
@ -425,8 +525,8 @@ Return a JSON object with a 'candidates' array of objects, each having 'ticker'
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
today = datetime.now().strftime("%Y-%m-%d")
|
today = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
|
||||||
# Get RSI
|
# Get RSI (and other indicators)
|
||||||
rsi_data = execute_tool("get_indicators", symbol=ticker, indicator="rsi", curr_date=today, look_back_days=14)
|
rsi_data = execute_tool("get_indicators", symbol=ticker, curr_date=today)
|
||||||
|
|
||||||
# Simple parsing of the string report to find the latest value
|
# Simple parsing of the string report to find the latest value
|
||||||
# The report format is usually "## rsi values...\n\nDATE: VALUE"
|
# The report format is usually "## rsi values...\n\nDATE: VALUE"
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ from typing import Dict, List, Optional, Callable, Any
|
||||||
from tradingagents.dataflows.y_finance import (
|
from tradingagents.dataflows.y_finance import (
|
||||||
get_YFin_data_online,
|
get_YFin_data_online,
|
||||||
get_stock_stats_indicators_window,
|
get_stock_stats_indicators_window,
|
||||||
|
get_technical_analysis,
|
||||||
get_balance_sheet as get_yfinance_balance_sheet,
|
get_balance_sheet as get_yfinance_balance_sheet,
|
||||||
get_cashflow as get_yfinance_cashflow,
|
get_cashflow as get_yfinance_cashflow,
|
||||||
get_income_statement as get_yfinance_income_statement,
|
get_income_statement as get_yfinance_income_statement,
|
||||||
|
|
@ -116,24 +117,34 @@ TOOL_REGISTRY: Dict[str, Dict[str, Any]] = {
|
||||||
|
|
||||||
# ========== TECHNICAL INDICATORS ==========
|
# ========== TECHNICAL INDICATORS ==========
|
||||||
|
|
||||||
|
# "get_indicators": {
|
||||||
|
# "description": "Get concise technical analysis with signals, trends, and key indicator interpretations",
|
||||||
|
# "category": "technical_indicators",
|
||||||
|
# "agents": ["market"],
|
||||||
|
# "vendors": {
|
||||||
|
# "yfinance": get_technical_analysis,
|
||||||
|
# },
|
||||||
|
# "vendor_priority": ["yfinance"],
|
||||||
|
# "parameters": {
|
||||||
|
# "symbol": {"type": "str", "description": "Ticker symbol"},
|
||||||
|
# "curr_date": {"type": "str", "description": "Current trading date, YYYY-mm-dd"},
|
||||||
|
# },
|
||||||
|
# "returns": "str: Concise analysis with RSI signals, MACD crossovers, MA trends, Bollinger position, and ATR volatility",
|
||||||
|
# },
|
||||||
|
|
||||||
"get_indicators": {
|
"get_indicators": {
|
||||||
"description": "Retrieve technical indicators for a given ticker symbol",
|
"description": "Get concise technical analysis with signals, trends, and key indicator interpretations",
|
||||||
"category": "technical_indicators",
|
"category": "technical_indicators",
|
||||||
"agents": ["market"],
|
"agents": ["market"],
|
||||||
"vendors": {
|
"vendors": {
|
||||||
"yfinance": get_stock_stats_indicators_window,
|
"yfinance": get_technical_analysis,
|
||||||
"alpha_vantage": get_alpha_vantage_indicator,
|
|
||||||
},
|
},
|
||||||
"vendor_priority": ["yfinance"],
|
"vendor_priority": ["yfinance"],
|
||||||
"execution_mode": "aggregate",
|
|
||||||
"aggregate_vendors": ["yfinance"],
|
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"symbol": {"type": "str", "description": "Ticker symbol"},
|
"symbol": {"type": "str", "description": "Ticker symbol"},
|
||||||
"indicator": {"type": "str", "description": "Technical indicator (rsi, macd, sma, ema, etc.)"},
|
|
||||||
"curr_date": {"type": "str", "description": "Current trading date, YYYY-mm-dd"},
|
"curr_date": {"type": "str", "description": "Current trading date, YYYY-mm-dd"},
|
||||||
"look_back_days": {"type": "int", "description": "Days to look back", "default": 30},
|
|
||||||
},
|
},
|
||||||
"returns": "str: Formatted report containing technical indicators",
|
"returns": "str: Concise analysis with RSI signals, MACD crossovers, MA trends, Bollinger position, and ATR volatility",
|
||||||
},
|
},
|
||||||
|
|
||||||
# ========== FUNDAMENTAL DATA ==========
|
# ========== FUNDAMENTAL DATA ==========
|
||||||
|
|
@ -226,9 +237,9 @@ TOOL_REGISTRY: Dict[str, Dict[str, Any]] = {
|
||||||
"openai": get_stock_news_openai,
|
"openai": get_stock_news_openai,
|
||||||
"google": get_google_news,
|
"google": get_google_news,
|
||||||
},
|
},
|
||||||
"vendor_priority": ["alpha_vantage", "reddit", "openai", "google"],
|
"vendor_priority": ["reddit", "openai", "google"],
|
||||||
"execution_mode": "aggregate",
|
"execution_mode": "aggregate",
|
||||||
"aggregate_vendors": ["alpha_vantage", "reddit", "google"],
|
"aggregate_vendors": ["reddit", "openai", "google"],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"query": {"type": "str", "description": "Search query or ticker symbol"},
|
"query": {"type": "str", "description": "Search query or ticker symbol"},
|
||||||
"start_date": {"type": "str", "description": "Start date, yyyy-mm-dd"},
|
"start_date": {"type": "str", "description": "Start date, yyyy-mm-dd"},
|
||||||
|
|
@ -262,9 +273,10 @@ TOOL_REGISTRY: Dict[str, Dict[str, Any]] = {
|
||||||
"category": "news_data",
|
"category": "news_data",
|
||||||
"agents": ["news"],
|
"agents": ["news"],
|
||||||
"vendors": {
|
"vendors": {
|
||||||
|
"yfinance": get_yfinance_insider_transactions,
|
||||||
"alpha_vantage": get_alpha_vantage_insider_transactions,
|
"alpha_vantage": get_alpha_vantage_insider_transactions,
|
||||||
},
|
},
|
||||||
"vendor_priority": ["alpha_vantage"],
|
"vendor_priority": ["yfinance"],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"ticker": {"type": "str", "description": "Ticker symbol"},
|
"ticker": {"type": "str", "description": "Ticker symbol"},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue