415 lines
14 KiB
Python
415 lines
14 KiB
Python
"""
|
|
Pump Detection Tools
|
|
Tools for identifying stocks that may experience sudden price increases.
|
|
Detects patterns like volume spikes, price acceleration, social sentiment surges, etc.
|
|
"""
|
|
|
|
from langchain_core.tools import tool
|
|
from typing import Annotated
|
|
from tradingagents.dataflows.interface import route_to_vendor
|
|
import json
|
|
|
|
|
|
@tool
|
|
def detect_volume_spike(
|
|
symbol: Annotated[str, "ticker symbol of the company"],
|
|
curr_date: Annotated[str, "The current trading date, YYYY-mm-dd"],
|
|
look_back_days: Annotated[int, "how many days to look back for comparison"] = 20,
|
|
threshold_multiplier: Annotated[float, "volume spike threshold (e.g., 2.0 = 2x average)"] = 2.0,
|
|
) -> str:
|
|
"""
|
|
Detect abnormal volume spikes that may indicate pump activity.
|
|
Volume spikes are early indicators of increased market interest.
|
|
|
|
Args:
|
|
symbol: Ticker symbol
|
|
curr_date: Current trading date
|
|
look_back_days: Historical period to calculate average volume
|
|
threshold_multiplier: How many times the average volume to flag as spike
|
|
|
|
Returns:
|
|
Analysis of volume spike patterns
|
|
"""
|
|
try:
|
|
# Get historical stock data
|
|
stock_data = route_to_vendor("get_stock_data", symbol, curr_date, look_back_days)
|
|
|
|
# Parse the CSV data
|
|
lines = stock_data.strip().split('\n')
|
|
if len(lines) < 2:
|
|
return f"Insufficient data for {symbol} to analyze volume spikes"
|
|
|
|
# Skip header and parse volumes
|
|
volumes = []
|
|
dates = []
|
|
for line in lines[1:]:
|
|
parts = line.split(',')
|
|
if len(parts) >= 6:
|
|
try:
|
|
volume = float(parts[5])
|
|
volumes.append(volume)
|
|
dates.append(parts[0])
|
|
except:
|
|
continue
|
|
|
|
if not volumes:
|
|
return f"Could not extract volume data for {symbol}"
|
|
|
|
# Calculate average volume (excluding today)
|
|
if len(volumes) > 1:
|
|
avg_volume = sum(volumes[:-1]) / len(volumes[:-1])
|
|
current_volume = volumes[-1]
|
|
else:
|
|
avg_volume = volumes[0]
|
|
current_volume = volumes[0]
|
|
|
|
spike_ratio = current_volume / avg_volume if avg_volume > 0 else 0
|
|
is_spike = spike_ratio >= threshold_multiplier
|
|
|
|
result = f"""## Volume Spike Analysis for {symbol}
|
|
|
|
**Current Volume**: {current_volume:,.0f} shares
|
|
**Average Volume (last {look_back_days} days)**: {avg_volume:,.0f} shares
|
|
**Spike Ratio**: {spike_ratio:.2f}x
|
|
**Threshold**: {threshold_multiplier}x
|
|
**SPIKE DETECTED**: {'YES ⚠️' if is_spike else 'NO'}
|
|
|
|
### Interpretation:
|
|
"""
|
|
if is_spike:
|
|
result += f"🚨 **PUMP SIGNAL**: Volume is {spike_ratio:.1f}x the average. This suggests increased institutional or retail interest."
|
|
else:
|
|
result += f"Normal volume activity. Current volume is {spike_ratio:.1f}x average (threshold: {threshold_multiplier}x)."
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
return f"Error analyzing volume spike for {symbol}: {str(e)}"
|
|
|
|
|
|
@tool
|
|
def detect_price_acceleration(
|
|
symbol: Annotated[str, "ticker symbol of the company"],
|
|
curr_date: Annotated[str, "The current trading date, YYYY-mm-dd"],
|
|
look_back_days: Annotated[int, "how many days to look back"] = 10,
|
|
) -> str:
|
|
"""
|
|
Detect rapid price acceleration that may indicate a pump.
|
|
Compares recent gains to longer-term performance.
|
|
|
|
Args:
|
|
symbol: Ticker symbol
|
|
curr_date: Current trading date
|
|
look_back_days: Historical period to analyze
|
|
|
|
Returns:
|
|
Analysis of price acceleration patterns
|
|
"""
|
|
try:
|
|
stock_data = route_to_vendor("get_stock_data", symbol, curr_date, look_back_days)
|
|
|
|
lines = stock_data.strip().split('\n')
|
|
if len(lines) < 3:
|
|
return f"Insufficient data for {symbol} to analyze price acceleration"
|
|
|
|
# Parse closing prices
|
|
prices = []
|
|
dates = []
|
|
for line in lines[1:]:
|
|
parts = line.split(',')
|
|
if len(parts) >= 5:
|
|
try:
|
|
close = float(parts[4])
|
|
prices.append(close)
|
|
dates.append(parts[0])
|
|
except:
|
|
continue
|
|
|
|
if len(prices) < 3:
|
|
return f"Could not extract sufficient price data for {symbol}"
|
|
|
|
prices.reverse() # Make chronological order
|
|
|
|
# Calculate gains
|
|
overall_gain = ((prices[-1] - prices[0]) / prices[0]) * 100 if prices[0] > 0 else 0
|
|
|
|
# Calculate recent acceleration (last 3 days vs before)
|
|
if len(prices) >= 4:
|
|
recent_gain = ((prices[-1] - prices[-3]) / prices[-3]) * 100 if prices[-3] > 0 else 0
|
|
older_gain = ((prices[-3] - prices[0]) / prices[0]) * 100 if prices[0] > 0 else 0
|
|
else:
|
|
recent_gain = overall_gain
|
|
older_gain = 0
|
|
|
|
is_accelerating = recent_gain > 5 and recent_gain > (older_gain * 1.5) if older_gain != 0 else recent_gain > 5
|
|
|
|
result = f"""## Price Acceleration Analysis for {symbol}
|
|
|
|
**Overall Gain ({look_back_days} days)**: {overall_gain:+.2f}%
|
|
**Recent Gain (last 3 days)**: {recent_gain:+.2f}%
|
|
**Older Gain (before that)**: {older_gain:+.2f}%
|
|
**ACCELERATION DETECTED**: {'YES ⚠️' if is_accelerating else 'NO'}
|
|
|
|
### Interpretation:
|
|
"""
|
|
if is_accelerating:
|
|
result += f"🚨 **PUMP SIGNAL**: Price accelerating rapidly with {recent_gain:.1f}% gain in last 3 days. Potential pump momentum building."
|
|
else:
|
|
result += f"Price movement steady or decelerating. Recent: {recent_gain:.1f}%, Overall: {overall_gain:.1f}%"
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
return f"Error analyzing price acceleration for {symbol}: {str(e)}"
|
|
|
|
|
|
@tool
|
|
def detect_social_sentiment_surge(
|
|
symbol: Annotated[str, "ticker symbol of the company"],
|
|
curr_date: Annotated[str, "The current trading date, YYYY-mm-dd"],
|
|
) -> str:
|
|
"""
|
|
Detect surges in social media sentiment that often precede pump moves.
|
|
Monitors Reddit, Twitter, StockTwits for unusual activity.
|
|
|
|
Args:
|
|
symbol: Ticker symbol
|
|
curr_date: Current trading date
|
|
|
|
Returns:
|
|
Analysis of social sentiment patterns
|
|
"""
|
|
try:
|
|
# Try to get trending social data
|
|
social_data = route_to_vendor("get_trending_social", curr_date, 3, 50)
|
|
|
|
# Check if our symbol is in trending data
|
|
is_trending = symbol.upper() in social_data.upper()
|
|
|
|
result = f"""## Social Sentiment Surge Analysis for {symbol}
|
|
|
|
**Trending Status**: {'YES 🔥' if is_trending else 'NO'}
|
|
|
|
### Current Market Buzz:
|
|
{social_data[:500]}...
|
|
|
|
### Interpretation:
|
|
"""
|
|
if is_trending:
|
|
result += f"🚨 **PUMP SIGNAL**: {symbol} is actively buzzing on social platforms. High retail interest detected."
|
|
else:
|
|
result += f"{symbol} is not currently in top trending stocks. Limited social momentum at this time."
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
return f"Error analyzing social sentiment for {symbol}: {str(e)}"
|
|
|
|
|
|
@tool
|
|
def detect_oversold_bounce(
|
|
symbol: Annotated[str, "ticker symbol of the company"],
|
|
curr_date: Annotated[str, "The current trading date, YYYY-mm-dd"],
|
|
rsi_threshold: Annotated[int, "RSI threshold for oversold (default 30)"] = 30,
|
|
) -> str:
|
|
"""
|
|
Detect oversold conditions that often precede pump bounces.
|
|
Uses RSI and other momentum indicators.
|
|
|
|
Args:
|
|
symbol: Ticker symbol
|
|
curr_date: Current trading date
|
|
rsi_threshold: RSI value threshold (below = oversold)
|
|
|
|
Returns:
|
|
Analysis of oversold bounce potential
|
|
"""
|
|
try:
|
|
# Get RSI indicator
|
|
rsi_data = route_to_vendor("get_indicators", symbol, "rsi", curr_date, 5)
|
|
|
|
# Parse RSI values to find current
|
|
lines = rsi_data.split('\n')
|
|
current_rsi = None
|
|
|
|
for line in lines:
|
|
if curr_date in line:
|
|
# Extract the RSI value
|
|
parts = line.split(':')
|
|
if len(parts) >= 2:
|
|
try:
|
|
current_rsi = float(parts[-1].strip())
|
|
break
|
|
except:
|
|
continue
|
|
|
|
if current_rsi is None:
|
|
# Try to get any RSI value
|
|
for line in lines:
|
|
if 'N/A' not in line and ':' in line and len(line.split(':')) >= 2:
|
|
try:
|
|
val = float(line.split(':')[-1].strip())
|
|
if 0 <= val <= 100:
|
|
current_rsi = val
|
|
break
|
|
except:
|
|
continue
|
|
|
|
is_oversold = current_rsi is not None and current_rsi < rsi_threshold
|
|
|
|
result = f"""## Oversold Bounce Analysis for {symbol}
|
|
|
|
**Current RSI**: {current_rsi:.1f if current_rsi else 'N/A'}
|
|
**Oversold Threshold**: {rsi_threshold}
|
|
**OVERSOLD**: {'YES ⚠️' if is_oversold else 'NO'}
|
|
|
|
### Interpretation:
|
|
"""
|
|
if is_oversold:
|
|
result += f"🚨 **PUMP SIGNAL**: RSI at {current_rsi:.1f} indicates oversold conditions. High probability of bounce/recovery pump."
|
|
else:
|
|
result += f"RSI at {current_rsi:.1f if current_rsi else 'N/A'} - not in oversold territory. Lower bounce probability."
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
return f"Error analyzing oversold bounce for {symbol}: {str(e)}"
|
|
|
|
|
|
@tool
|
|
def detect_catalyst_event(
|
|
symbol: Annotated[str, "ticker symbol of the company"],
|
|
curr_date: Annotated[str, "The current trading date, YYYY-mm-dd"],
|
|
) -> str:
|
|
"""
|
|
Detect upcoming or recent catalyst events that could trigger pumps.
|
|
Looks for earnings, FDA approvals, partnerships, etc.
|
|
|
|
Args:
|
|
symbol: Ticker symbol
|
|
curr_date: Current trading date
|
|
|
|
Returns:
|
|
Analysis of potential catalyst events
|
|
"""
|
|
try:
|
|
# Check earnings calendar
|
|
earnings_data = route_to_vendor("get_earnings_calendar", "3month", symbol)
|
|
|
|
has_catalyst = "earnings" in earnings_data.lower() or "scheduled" in earnings_data.lower()
|
|
|
|
result = f"""## Catalyst Event Analysis for {symbol}
|
|
|
|
**Upcoming Catalysts**: {'YES 📅' if has_catalyst else 'NO'}
|
|
|
|
### Catalyst Information:
|
|
{earnings_data[:400]}...
|
|
|
|
### Interpretation:
|
|
"""
|
|
if has_catalyst:
|
|
result += f"🚨 **PUMP SIGNAL**: Upcoming catalyst event detected. Catalysts drive pump volume and speculation."
|
|
else:
|
|
result += f"No major catalysts identified in the next 3 months."
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
return f"Error analyzing catalyst events for {symbol}: {str(e)}"
|
|
|
|
|
|
@tool
|
|
def calculate_pump_score(
|
|
symbol: Annotated[str, "ticker symbol"],
|
|
volume_spike_detected: Annotated[bool, "whether volume spike was detected"],
|
|
price_acceleration_detected: Annotated[bool, "whether price acceleration was detected"],
|
|
social_sentiment_surge: Annotated[bool, "whether social sentiment surge was detected"],
|
|
oversold_bounce: Annotated[bool, "whether oversold bounce signal was detected"],
|
|
catalyst_event: Annotated[bool, "whether catalyst event exists"],
|
|
) -> str:
|
|
"""
|
|
Calculate a composite pump probability score (0-100) based on detected signals.
|
|
|
|
Args:
|
|
symbol: Ticker symbol
|
|
volume_spike_detected: Boolean
|
|
price_acceleration_detected: Boolean
|
|
social_sentiment_surge: Boolean
|
|
oversold_bounce: Boolean
|
|
catalyst_event: Boolean
|
|
|
|
Returns:
|
|
Pump score and risk assessment
|
|
"""
|
|
# Score calculation
|
|
score = 0
|
|
signals_detected = 0
|
|
|
|
weights = {
|
|
"volume_spike": 25,
|
|
"price_acceleration": 20,
|
|
"social_sentiment": 15,
|
|
"oversold_bounce": 20,
|
|
"catalyst": 20,
|
|
}
|
|
|
|
if volume_spike_detected:
|
|
score += weights["volume_spike"]
|
|
signals_detected += 1
|
|
|
|
if price_acceleration_detected:
|
|
score += weights["price_acceleration"]
|
|
signals_detected += 1
|
|
|
|
if social_sentiment_surge:
|
|
score += weights["social_sentiment"]
|
|
signals_detected += 1
|
|
|
|
if oversold_bounce:
|
|
score += weights["oversold_bounce"]
|
|
signals_detected += 1
|
|
|
|
if catalyst_event:
|
|
score += weights["catalyst"]
|
|
signals_detected += 1
|
|
|
|
# Risk assessment
|
|
if score >= 70:
|
|
risk_level = "🔴 VERY HIGH RISK - HIGH PUMP PROBABILITY"
|
|
recommendation = "Strong indicators of potential pump. Exercise caution and use tight stop-losses."
|
|
elif score >= 50:
|
|
risk_level = "🟠 HIGH RISK - MODERATE PUMP PROBABILITY"
|
|
recommendation = "Multiple signals detected. Consider entry with risk management."
|
|
elif score >= 30:
|
|
risk_level = "🟡 MODERATE RISK - LOW PUMP PROBABILITY"
|
|
recommendation = "Some signals present. Requires additional confirmation before entry."
|
|
else:
|
|
risk_level = "🟢 LOW RISK - LOW PUMP PROBABILITY"
|
|
recommendation = "Limited pump signals. Better to wait for stronger setup."
|
|
|
|
result = f"""## Pump Detection Score for {symbol}
|
|
|
|
**Pump Probability Score**: {score}/100
|
|
**Signals Detected**: {signals_detected}/5
|
|
**Risk Level**: {risk_level}
|
|
|
|
### Signal Breakdown:
|
|
- Volume Spike: {'✅' if volume_spike_detected else '❌'}
|
|
- Price Acceleration: {'✅' if price_acceleration_detected else '❌'}
|
|
- Social Sentiment Surge: {'✅' if social_sentiment_surge else '❌'}
|
|
- Oversold Bounce Setup: {'✅' if oversold_bounce else '❌'}
|
|
- Catalyst Event: {'✅' if catalyst_event else '❌'}
|
|
|
|
### Recommendation:
|
|
{recommendation}
|
|
|
|
### Risk Management:
|
|
- Enter with small position size (1-2% of portfolio)
|
|
- Use tight stop-loss (2-3% below entry)
|
|
- Set profit targets at resistance levels
|
|
- Monitor volume and momentum continuously
|
|
- Exit on volume decline or reversal signals
|
|
"""
|
|
|
|
return result
|