File size: 9,628 Bytes
c90e00d
 
 
 
76b5330
dd350d1
c90e00d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76b5330
c90e00d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dd350d1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c90e00d
 
 
 
dd350d1
c90e00d
 
dd350d1
c90e00d
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
import yfinance as yf
import numpy as np
import pandas as pd
from typing import Dict, Any, List
from .state import AgentState, AgentState2
from .rag_analyzer import batch_analysis_chain

def calculate_rsi(prices, period=14):
    """Calculate Relative Strength Index."""
    # Calculate price changes
    delta = prices.diff()
    
    # Separate gains and losses
    gain = delta.clip(lower=0)
    loss = -delta.clip(upper=0)
    
    # Calculate average gain and loss
    avg_gain = gain.rolling(window=period).mean()
    avg_loss = loss.rolling(window=period).mean()
    
    # Calculate relative strength (RS)
    rs = avg_gain / avg_loss
    
    # Calculate RSI
    rsi = 100 - (100 / (1 + rs))
    
    return rsi

def calculate_macd(prices, fast=12, slow=26, signal=9):
    """Calculate Moving Average Convergence Divergence."""
    # Calculate EMAs
    ema_fast = prices.ewm(span=fast, adjust=False).mean()
    ema_slow = prices.ewm(span=slow, adjust=False).mean()
    
    # Calculate MACD line
    macd_line = ema_fast - ema_slow
    
    # Calculate signal line
    signal_line = macd_line.ewm(span=signal, adjust=False).mean()
    
    # Calculate histogram
    histogram = macd_line - signal_line
    
    return {
        'macd_line': macd_line,
        'signal_line': signal_line,
        'histogram': histogram
    }

def get_technical_indicators(ticker_obj, hist):
    """Calculate technical indicators for a stock without making judgments."""
    if hist.empty:
        return None
    
    # Calculate basic technical indicators
    hist['MA20'] = hist['Close'].rolling(window=20).mean()
    hist['MA50'] = hist['Close'].rolling(window=50).mean()
    hist['MA200'] = hist['Close'].rolling(window=200).mean()
    hist['RSI'] = calculate_rsi(hist['Close'])
    
    # Calculate MACD
    macd = calculate_macd(hist['Close'])
    hist['MACD_Line'] = macd['macd_line']
    hist['MACD_Signal'] = macd['signal_line']
    hist['MACD_Histogram'] = macd['histogram']
    
    # Calculate Bollinger Bands
    hist['BB_Middle'] = hist['Close'].rolling(window=20).mean()
    std = hist['Close'].rolling(window=20).std()
    hist['BB_Upper'] = hist['BB_Middle'] + (std * 2)
    hist['BB_Lower'] = hist['BB_Middle'] - (std * 2)
    
    # Get latest values
    latest = hist.iloc[-1]
    
    # Get fundamental data
    info = ticker_obj.info
    
    return {
        'current_price': latest['Close'],
        'technical_indicators': {
            'ma20': latest['MA20'],
            'ma50': latest['MA50'],
            'ma200': latest['MA200'],
            'rsi': latest['RSI'],
            'macd_line': latest['MACD_Line'],
            'macd_signal': latest['MACD_Signal'],
            'macd_histogram': latest['MACD_Histogram'],
            'bb_upper': latest['BB_Upper'],
            'bb_middle': latest['BB_Middle'],
            'bb_lower': latest['BB_Lower'],
            'volume': latest['Volume']
        },
        'fundamental_data': {
            'symbol': ticker_obj.ticker,
            'price': info.get('currentPrice'),
            'pe_ratio': info.get('trailingPE'),
            'peg_ratio': info.get('pegRatio'),
            'debt_to_equity': info.get('debtToEquity'),
            'forward_pe': info.get('forwardPE'),
            'beta': info.get('beta'),
            'return_on_equity': info.get('returnOnEquity'),
            'free_cash_flow': info.get('freeCashflow'),
            'revenue_growth': info.get('revenueGrowth'),
            'earnings_growth': info.get('earningsGrowth'),
            'dividend_yield': info.get('dividendYield'),
            'market_cap': info.get('marketCap'),
            'profit_margins': info.get('profitMargins'),
            'price_to_book': info.get('priceToBook')
        }
    }

def new_stock_analyzer(state: AgentState2) -> AgentState2:
    """Performs technical analysis only on the new high-ranked stocks from Zacks."""
    # Get the high-rank stocks from the Zacks analyzer
    high_rank_stocks = state.get("high_rank_stocks", [])
    
    if not high_rank_stocks:
        state["messages"] = state.get("messages", []) + [{
            "role": "ai",
            "content": "[NewStockAnalyzer] No high-ranked stocks found to analyze."
        }]
        state["new_stock_analysis"] = {}
        return state
    
    # Extract tickers from high-rank stocks
    tickers = [stock['symbol'] for stock in high_rank_stocks]
    analysis_results = {}
    
    # Collect technical data for all new stocks
    for ticker in tickers:
        try:
            # Get stock data
            stock = yf.Ticker(ticker)
            hist = stock.history(period="6mo")
            
            if not hist.empty:
                # Get comprehensive technical indicators without judgments
                indicators = get_technical_indicators(stock, hist)
                
                if indicators:
                    analysis_results[ticker] = indicators
                else:
                    analysis_results[ticker] = {"error": "Failed to calculate indicators"}
            else:
                analysis_results[ticker] = {"error": "No historical data available"}
                
        except Exception as e:
            print(f"Error analyzing {ticker}: {str(e)}")
            analysis_results[ticker] = {"error": str(e)}
    
    # Add RAG interpretation for new stocks
    risk_level = state.get("risk_level", 5)
    investment_goals = state.get("investment_goals", "Growth")
    
    # Prepare batch analysis data for new stocks
    stocks_data = ""
    for ticker, data in analysis_results.items():
        if "error" not in data:
            stocks_data += f"Stock: {ticker}\n"
            stocks_data += f"Current Price: ${data['current_price']:.2f}\n"
            stocks_data += "Technical Indicators:\n"
            for key, value in data['technical_indicators'].items():
                stocks_data += f"  {key}: {value}\n"
            stocks_data += "Fundamental Data:\n"
            for key, value in data['fundamental_data'].items():
                if value is not None:
                    stocks_data += f"  {key}: {value}\n"
            stocks_data += "\n---\n\n"
    
    # Only make the batch analysis call if we have stocks to analyze
    if stocks_data:
        try:
            # Get batch analysis for all new stocks in one call
            batch_analysis_result = batch_analysis_chain.invoke({
                "stocks_data": stocks_data,
                "risk_level": risk_level,
                "investment_goals": investment_goals
            })
            
            # Try to parse as JSON first
            try:
                import json
                import re
                
                # Try to find JSON-like content in the response using regex
                json_match = re.search(r'\{[\s\S]*\}', batch_analysis_result)
                if json_match:
                    json_str = json_match.group(0)
                    analysis_data = json.loads(json_str)
                    
                    # Update analysis_results with the parsed JSON
                    for ticker, analysis in analysis_data.items():
                        if ticker in analysis_results:
                            analysis_results[ticker]["rag_interpretation"] = analysis
                else:
                    # Fallback to text parsing approach
                    current_ticker = None
                    current_analysis = []
                    
                    for line in batch_analysis_result.split('\n'):
                        if ':' in line and line.split(':')[0].strip() in analysis_results:
                            # If we have a previous ticker, save its analysis
                            if current_ticker is not None and current_ticker in analysis_results:
                                analysis_results[current_ticker]["rag_interpretation"] = '\n'.join(current_analysis).strip()
                                current_analysis = []
                            
                            # Start new ticker
                            current_ticker = line.split(':')[0].strip()
                            current_analysis.append(line.split(':', 1)[1].strip())
                        elif current_ticker is not None:
                            current_analysis.append(line)
                    
                    # Add the last ticker's analysis
                    if current_ticker is not None and current_ticker in analysis_results:
                        analysis_results[current_ticker]["rag_interpretation"] = '\n'.join(current_analysis).strip()
            
            except (json.JSONDecodeError, Exception) as json_err:
                print(f"Error parsing JSON response for new stocks: {str(json_err)}")
                print(f"Raw response: {batch_analysis_result}")
                
        except Exception as e:
            # Log any errors but continue execution
            import traceback
            print(f"Error in batch analysis for new stocks: {str(e)}")
            print(traceback.format_exc())
    
    # Update state with new stock analysis
    state["new_stock_analysis"] = analysis_results
    
    # Add message to communication
    num_with_rag = sum(1 for ticker in analysis_results if "rag_interpretation" in analysis_results[ticker])
    state["messages"] = state.get("messages", []) + [{
        "role": "ai",
        "content": f"[NewStockAnalyzer] I've calculated technical indicators, gathered fundamental data, and added RAG-based interpretations for {num_with_rag} of {len(analysis_results)} new high-ranked stocks."
    }]
    
    return state