TradingAgents/autonomous/signal_processor.py

414 lines
14 KiB
Python

"""
Signal Processor
===============
Processes signals from multiple sources and generates actionable trading recommendations
with specific entry/exit prices using TradingAgents AI.
"""
import asyncio
import logging
from typing import Dict, List, Optional, Any, Tuple
from datetime import datetime, timedelta
from dataclasses import dataclass
import statistics
# Import TradingAgents
import sys
sys.path.append('..')
from tradingagents.graph.trading_graph import TradingAgentsGraph
from tradingagents.default_config import DEFAULT_CONFIG
# Import our modules
from .ibkr_connector import IBKRConnector, Position
from .data_aggregator import DataAggregator, MarketSignal
logger = logging.getLogger(__name__)
@dataclass
class TradingRecommendation:
"""Complete trading recommendation with entry/exit points"""
ticker: str
action: str # 'BUY', 'SELL', 'HOLD'
current_price: float
entry_price_min: float
entry_price_max: float
target_price_1: float
target_price_2: float
stop_loss: float
confidence: float # 0-100
position_size: float # Percentage of portfolio
reasoning: str
data_sources: List[str]
risk_level: str # 'LOW', 'MEDIUM', 'HIGH'
timestamp: datetime
class SignalProcessor:
"""
Processes signals and generates trading recommendations
"""
def __init__(self,
ibkr_connector: IBKRConnector,
data_aggregator: DataAggregator,
config: Optional[Dict] = None):
"""
Initialize signal processor
Args:
ibkr_connector: IBKR connection instance
data_aggregator: Data aggregator instance
config: Configuration dictionary
"""
self.ibkr = ibkr_connector
self.data_agg = data_aggregator
self.config = config or DEFAULT_CONFIG.copy()
# Configure TradingAgents for fast processing
self.config['deep_think_llm'] = 'gpt-4o-mini'
self.config['quick_think_llm'] = 'gpt-4o-mini'
self.config['max_debate_rounds'] = 1
self.trading_agents = TradingAgentsGraph(debug=False, config=self.config)
self.recommendations: List[TradingRecommendation] = []
async def calculate_technical_levels(self, ticker: str) -> Dict[str, float]:
"""
Calculate technical support/resistance levels
Args:
ticker: Stock ticker
Returns:
Dictionary with technical levels
"""
try:
import yfinance as yf
stock = yf.Ticker(ticker)
# Get recent price data
hist = stock.history(period="3mo")
if hist.empty:
return {}
current_price = hist['Close'].iloc[-1]
high_3m = hist['High'].max()
low_3m = hist['Low'].min()
# Calculate moving averages
ma_20 = hist['Close'].tail(20).mean()
ma_50 = hist['Close'].tail(50).mean() if len(hist) >= 50 else ma_20
# Calculate support/resistance levels
# Using pivot points
last_high = hist['High'].iloc[-1]
last_low = hist['Low'].iloc[-1]
last_close = hist['Close'].iloc[-1]
pivot = (last_high + last_low + last_close) / 3
resistance_1 = 2 * pivot - last_low
resistance_2 = pivot + (last_high - last_low)
support_1 = 2 * pivot - last_high
support_2 = pivot - (last_high - last_low)
# Calculate RSI
delta = hist['Close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))
current_rsi = rsi.iloc[-1]
return {
'current_price': current_price,
'resistance_1': resistance_1,
'resistance_2': resistance_2,
'support_1': support_1,
'support_2': support_2,
'ma_20': ma_20,
'ma_50': ma_50,
'high_3m': high_3m,
'low_3m': low_3m,
'rsi': current_rsi,
'pivot': pivot
}
except Exception as e:
logger.error(f"Error calculating technical levels for {ticker}: {e}")
return {}
async def analyze_position_sizing(self,
ticker: str,
confidence: float,
risk_level: str) -> float:
"""
Calculate appropriate position size based on portfolio and risk
Args:
ticker: Stock ticker
confidence: Signal confidence (0-100)
risk_level: Risk level of the trade
Returns:
Position size as percentage of portfolio
"""
# Get current portfolio info
await self.ibkr.sync_portfolio()
portfolio_summary = self.ibkr.get_portfolio_summary()
# Check if we already have a position
existing_position = await self.ibkr.get_position(ticker)
# Base position sizing rules
max_position = 0.20 # Max 20% in any single position
base_size = 0.10 # Base 10% position
# Adjust based on confidence
if confidence > 90:
size_multiplier = 1.5
elif confidence > 80:
size_multiplier = 1.2
elif confidence > 70:
size_multiplier = 1.0
else:
size_multiplier = 0.7
# Adjust based on risk
risk_multipliers = {
'LOW': 1.2,
'MEDIUM': 1.0,
'HIGH': 0.6
}
risk_multiplier = risk_multipliers.get(risk_level, 1.0)
# Calculate position size
position_size = base_size * size_multiplier * risk_multiplier
# If we already have a position, consider scaling
if existing_position:
current_weight = existing_position.market_value / portfolio_summary['total_value']
remaining_capacity = max_position - current_weight
position_size = min(position_size, remaining_capacity)
# Ensure within limits
position_size = max(0.05, min(position_size, max_position)) # Between 5% and 20%
return round(position_size, 3)
async def process_signals(self, ticker: str) -> Optional[TradingRecommendation]:
"""
Process all signals for a ticker and generate recommendation
Args:
ticker: Stock ticker
Returns:
Trading recommendation if signals are strong enough
"""
try:
# Get all signals for this ticker
signals = [s for s in self.data_agg.market_signals if s.ticker == ticker]
if not signals:
logger.info(f"No signals for {ticker}")
return None
# Calculate technical levels
tech_levels = await self.calculate_technical_levels(ticker)
if not tech_levels:
logger.warning(f"Could not calculate technical levels for {ticker}")
return None
current_price = tech_levels['current_price']
# Run TradingAgents analysis
logger.info(f"Running TradingAgents analysis for {ticker}")
_, ai_decision = self.trading_agents.propagate(ticker, datetime.now().strftime('%Y-%m-%d'))
# Combine AI decision with our signals
avg_confidence = statistics.mean([s.confidence for s in signals])
# Determine action based on signals and AI
buy_signals = [s for s in signals if s.action == 'BUY']
sell_signals = [s for s in signals if s.action == 'SELL']
if len(buy_signals) > len(sell_signals) and 'buy' in ai_decision.lower():
action = 'BUY'
# Entry points near support or current price
entry_min = min(current_price * 0.995, tech_levels['support_1'])
entry_max = current_price * 1.005
# Targets based on resistance levels
target_1 = tech_levels['resistance_1']
target_2 = tech_levels['resistance_2']
# Stop loss below support
stop_loss = tech_levels['support_1'] * 0.98
elif len(sell_signals) > len(buy_signals) or 'sell' in ai_decision.lower():
action = 'SELL'
# For selling, entry is current price range
entry_min = current_price * 0.995
entry_max = current_price * 1.01
# Targets for selling (lower prices)
target_1 = tech_levels['support_1']
target_2 = tech_levels['support_2']
# Stop loss above resistance for shorts
stop_loss = tech_levels['resistance_1'] * 1.02
else:
action = 'HOLD'
entry_min = entry_max = current_price
target_1 = target_2 = current_price
stop_loss = current_price * 0.95
# Determine risk level
rsi = tech_levels.get('rsi', 50)
if rsi > 70 or rsi < 30:
risk_level = 'HIGH'
elif 40 <= rsi <= 60:
risk_level = 'LOW'
else:
risk_level = 'MEDIUM'
# Calculate position size
position_size = await self.analyze_position_sizing(ticker, avg_confidence, risk_level)
# Build reasoning
reasoning_parts = [
f"AI Analysis: {ai_decision[:200]}",
f"Signals: {len(buy_signals)} buy, {len(sell_signals)} sell"
]
for signal in signals[:2]: # Include top 2 signals
if signal.signal_type == 'congressional':
reasoning_parts.append(f"Congressional activity detected")
elif signal.signal_type == 'insider':
reasoning_parts.append(f"Insider buying activity")
elif signal.signal_type == 'sentiment':
reasoning_parts.append(f"Positive market sentiment")
reasoning_parts.append(f"RSI: {rsi:.1f}")
# Create recommendation
recommendation = TradingRecommendation(
ticker=ticker,
action=action,
current_price=current_price,
entry_price_min=round(entry_min, 2),
entry_price_max=round(entry_max, 2),
target_price_1=round(target_1, 2),
target_price_2=round(target_2, 2),
stop_loss=round(stop_loss, 2),
confidence=avg_confidence,
position_size=position_size,
reasoning=' | '.join(reasoning_parts),
data_sources=[s.signal_type for s in signals],
risk_level=risk_level,
timestamp=datetime.now()
)
self.recommendations.append(recommendation)
return recommendation
except Exception as e:
logger.error(f"Error processing signals for {ticker}: {e}")
return None
async def process_portfolio(self) -> List[TradingRecommendation]:
"""
Process entire portfolio and generate recommendations
Returns:
List of trading recommendations
"""
# Get current positions
await self.ibkr.sync_portfolio()
positions = self.ibkr.positions
# Get tickers from portfolio
portfolio_tickers = list(positions.keys())
# Also analyze some high-opportunity tickers even if not in portfolio
watchlist = ["NVDA", "TSLA", "AAPL", "MSFT", "AVGO"]
all_tickers = list(set(portfolio_tickers + watchlist))
# Get signals for all tickers
await self.data_agg.aggregate_signals(all_tickers)
# Process each ticker
recommendations = []
for ticker in all_tickers:
logger.info(f"Processing {ticker}")
rec = await self.process_signals(ticker)
if rec and rec.action != 'HOLD':
recommendations.append(rec)
# Sort by confidence
recommendations.sort(key=lambda x: x.confidence, reverse=True)
logger.info(f"Generated {len(recommendations)} recommendations")
return recommendations
def format_recommendation(self, rec: TradingRecommendation) -> str:
"""
Format recommendation for display
Args:
rec: Trading recommendation
Returns:
Formatted string
"""
# Calculate percentage gains
gain_1 = ((rec.target_price_1 - rec.current_price) / rec.current_price) * 100
gain_2 = ((rec.target_price_2 - rec.current_price) / rec.current_price) * 100
loss = ((rec.stop_loss - rec.current_price) / rec.current_price) * 100
formatted = f"""
━━━━━━━━━━━━━━━━━━━━━━━
🎯 ACTION: {rec.action}
📈 TICKER: {rec.ticker}
💰 CURRENT: ${rec.current_price:.2f}
📍 ENTRY: ${rec.entry_price_min:.2f} - ${rec.entry_price_max:.2f}
🎯 TARGET 1: ${rec.target_price_1:.2f} ({gain_1:+.1f}%)
🎯 TARGET 2: ${rec.target_price_2:.2f} ({gain_2:+.1f}%)
🛑 STOP LOSS: ${rec.stop_loss:.2f} ({loss:.1f}%)
📊 CONFIDENCE: {rec.confidence:.0f}%
💼 POSITION SIZE: {rec.position_size:.1%} of portfolio
⚠️ RISK: {rec.risk_level}
📝 REASONING:
{rec.reasoning}
📅 Generated: {rec.timestamp.strftime('%Y-%m-%d %H:%M')}
━━━━━━━━━━━━━━━━━━━━━━━
"""
return formatted
# Example usage
async def main():
"""Example of using the signal processor"""
# Initialize components
ibkr = IBKRConnector()
data_agg = DataAggregator()
processor = SignalProcessor(ibkr, data_agg)
# Connect to IBKR (would need TWS running)
# await ibkr.connect()
# Process a single ticker
rec = await processor.process_signals("NVDA")
if rec:
print(processor.format_recommendation(rec))
if __name__ == "__main__":
asyncio.run(main())