Spaces:
Running
Running
| import yfinance as yf | |
| import pandas as pd | |
| import numpy as np | |
| import torch | |
| from datetime import datetime, timedelta | |
| import plotly.graph_objects as go | |
| import plotly.express as px | |
| from plotly.subplots import make_subplots | |
| def get_indonesian_stocks(): | |
| """Get list of major Indonesian stocks""" | |
| return { | |
| "BBCA.JK": "Bank Central Asia", | |
| "BBRI.JK": "Bank BRI", | |
| "BBNI.JK": "Bank BNI", | |
| "BMRI.JK": "Bank Mandiri", | |
| "TLKM.JK": "Telkom Indonesia", | |
| "UNVR.JK": "Unilever Indonesia", | |
| "ASII.JK": "Astra International", | |
| "INDF.JK": "Indofood Sukses Makmur", | |
| "KLBF.JK": "Kalbe Farma", | |
| "HMSP.JK": "HM Sampoerna", | |
| "GGRM.JK": "Gudang Garam", | |
| "ADRO.JK": "Adaro Energy", | |
| "PGAS.JK": "Perusahaan Gas Negara", | |
| "JSMR.JK": "Jasa Marga", | |
| "WIKA.JK": "Wijaya Karya", | |
| "PTBA.JK": "Tambang Batubara Bukit Asam", | |
| "ANTM.JK": "Aneka Tambang", | |
| "SMGR.JK": "Semen Indonesia", | |
| "INTP.JK": "Indocement Tunggal Prakasa", | |
| "ITMG.JK": "Indo Tambangraya Megah" | |
| } | |
| def calculate_technical_indicators(data): | |
| """Calculate various technical indicators""" | |
| indicators = {} | |
| # RSI (Relative Strength Index) | |
| def calculate_rsi(prices, period=14): | |
| delta = prices.diff() | |
| gain = (delta.where(delta > 0, 0)).rolling(window=period).mean() | |
| loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean() | |
| rs = gain / loss | |
| rsi = 100 - (100 / (1 + rs)) | |
| return rsi | |
| indicators['rsi'] = { | |
| 'current': calculate_rsi(data['Close']).iloc[-1], | |
| 'values': calculate_rsi(data['Close']) | |
| } | |
| # MACD | |
| def calculate_macd(prices, fast=12, slow=26, signal=9): | |
| exp1 = prices.ewm(span=fast).mean() | |
| exp2 = prices.ewm(span=slow).mean() | |
| macd = exp1 - exp2 | |
| signal_line = macd.ewm(span=signal).mean() | |
| histogram = macd - signal_line | |
| return macd, signal_line, histogram | |
| macd, signal_line, histogram = calculate_macd(data['Close']) | |
| indicators['macd'] = { | |
| 'macd': macd.iloc[-1], | |
| 'signal': signal_line.iloc[-1], | |
| 'histogram': histogram.iloc[-1], | |
| 'signal_text': 'BUY' if histogram.iloc[-1] > 0 else 'SELL' | |
| } | |
| # Bollinger Bands | |
| def calculate_bollinger_bands(prices, period=20, std_dev=2): | |
| sma = prices.rolling(window=period).mean() | |
| std = prices.rolling(window=period).std() | |
| upper_band = sma + (std * std_dev) | |
| lower_band = sma - (std * std_dev) | |
| return upper_band, sma, lower_band | |
| upper, middle, lower = calculate_bollinger_bands(data['Close']) | |
| current_price = data['Close'].iloc[-1] | |
| bb_position = (current_price - lower.iloc[-1]) / (upper.iloc[-1] - lower.iloc[-1]) | |
| indicators['bollinger'] = { | |
| 'upper': upper.iloc[-1], | |
| 'middle': middle.iloc[-1], | |
| 'lower': lower.iloc[-1], | |
| 'position': 'UPPER' if bb_position > 0.8 else 'LOWER' if bb_position < 0.2 else 'MIDDLE' | |
| } | |
| # Moving Averages | |
| indicators['moving_averages'] = { | |
| 'sma_20': data['Close'].rolling(20).mean().iloc[-1], | |
| 'sma_50': data['Close'].rolling(50).mean().iloc[-1], | |
| 'sma_200': data['Close'].rolling(200).mean().iloc[-1], | |
| 'ema_12': data['Close'].ewm(span=12).mean().iloc[-1], | |
| 'ema_26': data['Close'].ewm(span=26).mean().iloc[-1] | |
| } | |
| # Volume indicators | |
| indicators['volume'] = { | |
| 'current': data['Volume'].iloc[-1], | |
| 'avg_20': data['Volume'].rolling(20).mean().iloc[-1], | |
| 'ratio': data['Volume'].iloc[-1] / data['Volume'].rolling(20).mean().iloc[-1] | |
| } | |
| return indicators | |
| def generate_trading_signals(data, indicators): | |
| """Generate trading signals based on technical indicators""" | |
| signals = {} | |
| current_price = data['Close'].iloc[-1] | |
| # Initialize scores | |
| buy_signals = 0 | |
| sell_signals = 0 | |
| signal_details = [] | |
| # RSI Signal | |
| rsi = indicators['rsi']['current'] | |
| if rsi < 30: | |
| buy_signals += 1 | |
| signal_details.append(f"✅ RSI ({rsi:.1f}) - Oversold - BUY signal") | |
| elif rsi > 70: | |
| sell_signals += 1 | |
| signal_details.append(f"❌ RSI ({rsi:.1f}) - Overbought - SELL signal") | |
| else: | |
| signal_details.append(f"⚪ RSI ({rsi:.1f}) - Neutral") | |
| # MACD Signal | |
| macd_hist = indicators['macd']['histogram'] | |
| if macd_hist > 0: | |
| buy_signals += 1 | |
| signal_details.append(f"✅ MACD Histogram ({macd_hist:.4f}) - Positive - BUY signal") | |
| else: | |
| sell_signals += 1 | |
| signal_details.append(f"❌ MACD Histogram ({macd_hist:.4f}) - Negative - SELL signal") | |
| # Bollinger Bands Signal | |
| bb_position = indicators['bollinger']['position'] | |
| if bb_position == 'LOWER': | |
| buy_signals += 1 | |
| signal_details.append(f"✅ Bollinger Bands - Near lower band - BUY signal") | |
| elif bb_position == 'UPPER': | |
| sell_signals += 1 | |
| signal_details.append(f"❌ Bollinger Bands - Near upper band - SELL signal") | |
| else: | |
| signal_details.append("⚪ Bollinger Bands - Middle position") | |
| # Moving Averages Signal | |
| sma_20 = indicators['moving_averages']['sma_20'] | |
| sma_50 = indicators['moving_averages']['sma_50'] | |
| if current_price > sma_20 > sma_50: | |
| buy_signals += 1 | |
| signal_details.append(f"✅ Price above MA(20,50) - Bullish - BUY signal") | |
| elif current_price < sma_20 < sma_50: | |
| sell_signals += 1 | |
| signal_details.append(f"❌ Price below MA(20,50) - Bearish - SELL signal") | |
| else: | |
| signal_details.append("⚪ Moving Averages - Mixed signals") | |
| # Volume Signal | |
| volume_ratio = indicators['volume']['ratio'] | |
| if volume_ratio > 1.5: | |
| buy_signals += 0.5 | |
| signal_details.append(f"✅ High volume ({volume_ratio:.1f}x avg) - Strengthens BUY signal") | |
| elif volume_ratio < 0.5: | |
| sell_signals += 0.5 | |
| signal_details.append(f"❌ Low volume ({volume_ratio:.1f}x avg) - Weakens SELL signal") | |
| else: | |
| signal_details.append(f"⚪ Normal volume ({volume_ratio:.1f}x avg)") | |
| # Determine overall signal | |
| total_signals = buy_signals + sell_signals | |
| signal_strength = (buy_signals / max(total_signals, 1)) * 100 | |
| if buy_signals > sell_signals: | |
| overall_signal = "BUY" | |
| elif sell_signals > buy_signals: | |
| overall_signal = "SELL" | |
| else: | |
| overall_signal = "HOLD" | |
| # Calculate support and resistance | |
| recent_high = data['High'].tail(20).max() | |
| recent_low = data['Low'].tail(20).min() | |
| signals = { | |
| 'overall': overall_signal, | |
| 'strength': signal_strength, | |
| 'details': '\n'.join(signal_details), | |
| 'support': recent_low, | |
| 'resistance': recent_high, | |
| 'stop_loss': recent_low * 0.95 if overall_signal == "BUY" else recent_high * 1.05 | |
| } | |
| return signals | |
| def get_fundamental_data(stock): | |
| """Get fundamental data for the stock""" | |
| try: | |
| info = stock.info | |
| history = stock.history(period="1d") | |
| fundamental_info = { | |
| 'name': info.get('longName', 'N/A'), | |
| 'current_price': history['Close'].iloc[-1] if not history.empty else 0, | |
| 'market_cap': info.get('marketCap', 0), | |
| 'pe_ratio': info.get('forwardPE', 0), | |
| 'dividend_yield': info.get('dividendYield', 0) * 100 if info.get('dividendYield') else 0, | |
| 'volume': history['Volume'].iloc[-1] if not history.empty else 0, | |
| 'info': f""" | |
| Sector: {info.get('sector', 'N/A')} | |
| Industry: {info.get('industry', 'N/A')} | |
| Market Cap: {format_large_number(info.get('marketCap', 0))} | |
| 52 Week High: {info.get('fiftyTwoWeekHigh', 'N/A')} | |
| 52 Week Low: {info.get('fiftyTwoWeekLow', 'N/A')} | |
| Beta: {info.get('beta', 'N/A')} | |
| EPS: {info.get('forwardEps', 'N/A')} | |
| Book Value: {info.get('bookValue', 'N/A')} | |
| Price to Book: {info.get('priceToBook', 'N/A')} | |
| """.strip() | |
| } | |
| return fundamental_info | |
| except Exception as e: | |
| print(f"Error getting fundamental data: {e}") | |
| return { | |
| 'name': 'N/A', | |
| 'current_price': 0, | |
| 'market_cap': 0, | |
| 'pe_ratio': 0, | |
| 'dividend_yield': 0, | |
| 'volume': 0, | |
| 'info': 'Unable to fetch fundamental data' | |
| } | |
| def format_large_number(num): | |
| """Format large numbers to readable format""" | |
| if num >= 1e12: | |
| return f"{num/1e12:.2f}T" | |
| elif num >= 1e9: | |
| return f"{num/1e9:.2f}B" | |
| elif num >= 1e6: | |
| return f"{num/1e6:.2f}M" | |
| elif num >= 1e3: | |
| return f"{num/1e3:.2f}K" | |
| else: | |
| return f"{num:.2f}" | |
| def predict_prices(data, model, tokenizer, prediction_days=30): | |
| """Predict future prices using Chronos-Bolt model""" | |
| try: | |
| # Prepare data for prediction | |
| prices = data['Close'].values | |
| context_length = min(len(prices), 512) | |
| # Tokenize the input | |
| input_sequence = prices[-context_length:] | |
| # Create prediction input | |
| prediction_input = torch.tensor(input_sequence).unsqueeze(0).float() | |
| # Generate predictions | |
| with torch.no_grad(): | |
| forecast = model.generate( | |
| prediction_input, | |
| prediction_length=prediction_days, | |
| temperature=1.0, | |
| top_k=50, | |
| top_p=0.9 | |
| ) | |
| predictions = forecast[0].numpy() | |
| # Calculate prediction statistics | |
| last_price = prices[-1] | |
| predicted_high = np.max(predictions) | |
| predicted_low = np.min(predictions) | |
| predicted_mean = np.mean(predictions) | |
| change_pct = ((predicted_mean - last_price) / last_price) * 100 | |
| return { | |
| 'values': predictions, | |
| 'dates': pd.date_range( | |
| start=data.index[-1] + timedelta(days=1), | |
| periods=prediction_days, | |
| freq='D' | |
| ), | |
| 'high_30d': predicted_high, | |
| 'low_30d': predicted_low, | |
| 'mean_30d': predicted_mean, | |
| 'change_pct': change_pct, | |
| 'summary': f""" | |
| AI Model: Amazon Chronos-Bolt | |
| Prediction Period: {prediction_days} days | |
| Expected Change: {change_pct:.2f}% | |
| Confidence: Medium (based on historical patterns) | |
| Note: AI predictions are for reference only and not financial advice | |
| """.strip() | |
| } | |
| except Exception as e: | |
| print(f"Error in prediction: {e}") | |
| return { | |
| 'values': [], | |
| 'dates': [], | |
| 'high_30d': 0, | |
| 'low_30d': 0, | |
| 'mean_30d': 0, | |
| 'change_pct': 0, | |
| 'summary': 'Prediction unavailable due to model error' | |
| } | |
| def create_price_chart(data, indicators): | |
| """Create price chart with technical indicators""" | |
| fig = make_subplots( | |
| rows=3, cols=1, | |
| shared_xaxes=True, | |
| vertical_spacing=0.05, | |
| subplot_titles=('Price & Moving Averages', 'RSI', 'MACD'), | |
| row_width=[0.2, 0.2, 0.7] | |
| ) | |
| # Price and Moving Averages | |
| fig.add_trace( | |
| go.Candlestick( | |
| x=data.index, | |
| open=data['Open'], | |
| high=data['High'], | |
| low=data['Low'], | |
| close=data['Close'], | |
| name='Price' | |
| ), | |
| row=1, col=1 | |
| ) | |
| # Add moving averages | |
| fig.add_trace( | |
| go.Scatter( | |
| x=data.index, | |
| y=indicators['moving_averages']['sma_20'], | |
| name='SMA 20', | |
| line=dict(color='orange', width=1) | |
| ), | |
| row=1, col=1 | |
| ) | |
| fig.add_trace( | |
| go.Scatter( | |
| x=data.index, | |
| y=indicators['moving_averages']['sma_50'], | |
| name='SMA 50', | |
| line=dict(color='blue', width=1) | |
| ), | |
| row=1, col=1 | |
| ) | |
| # RSI | |
| fig.add_trace( | |
| go.Scatter( | |
| x=data.index, | |
| y=indicators['rsi']['values'], | |
| name='RSI', | |
| line=dict(color='purple') | |
| ), | |
| row=2, col=1 | |
| ) | |
| fig.add_hline(y=70, line_dash="dash", line_color="red", row=2, col=1) | |
| fig.add_hline(y=30, line_dash="dash", line_color="green", row=2, col=1) | |
| # MACD | |
| fig.add_trace( | |
| go.Scatter( | |
| x=data.index, | |
| y=indicators['macd']['macd'], | |
| name='MACD', | |
| line=dict(color='blue') | |
| ), | |
| row=3, col=1 | |
| ) | |
| fig.add_trace( | |
| go.Scatter( | |
| x=data.index, | |
| y=indicators['macd']['signal'], | |
| name='Signal', | |
| line=dict(color='red') | |
| ), | |
| row=3, col=1 | |
| ) | |
| fig.update_layout( | |
| title='Technical Analysis Dashboard', | |
| height=900, | |
| showlegend=True, | |
| xaxis_rangeslider_visible=False | |
| ) | |
| return fig | |
| def create_technical_chart(data, indicators): | |
| """Create technical indicators dashboard""" | |
| fig = make_subplots( | |
| rows=2, cols=2, | |
| subplot_titles=('Bollinger Bands', 'Volume', 'Price vs MA', 'RSI Analysis'), | |
| specs=[[{"secondary_y": False}, {"secondary_y": False}], | |
| [{"secondary_y": False}, {"secondary_y": False}]] | |
| ) | |
| # Bollinger Bands | |
| fig.add_trace( | |
| go.Scatter(x=data.index, y=data['Close'], name='Price', line=dict(color='black')), | |
| row=1, col=1 | |
| ) | |
| # Volume | |
| fig.add_trace( | |
| go.Bar(x=data.index, y=data['Volume'], name='Volume', marker_color='lightblue'), | |
| row=1, col=2 | |
| ) | |
| # Price vs Moving Averages | |
| fig.add_trace( | |
| go.Scatter(x=data.index, y=data['Close'], name='Price', line=dict(color='black')), | |
| row=2, col=1 | |
| ) | |
| fig.add_trace( | |
| go.Scatter( | |
| x=data.index, | |
| y=[indicators['moving_averages']['sma_20']] * len(data), | |
| name='SMA 20', | |
| line=dict(color='orange', dash='dash') | |
| ), | |
| row=2, col=1 | |
| ) | |
| fig.update_layout( | |
| title='Technical Indicators Overview', | |
| height=600, | |
| showlegend=False | |
| ) | |
| return fig | |
| def create_prediction_chart(data, predictions): | |
| """Create prediction visualization""" | |
| if not predictions['values'].size: | |
| return go.Figure() | |
| fig = go.Figure() | |
| # Historical prices | |
| fig.add_trace( | |
| go.Scatter( | |
| x=data.index[-60:], | |
| y=data['Close'].values[-60:], | |
| name='Historical Price', | |
| line=dict(color='blue', width=2) | |
| ) | |
| ) | |
| # Predictions | |
| fig.add_trace( | |
| go.Scatter( | |
| x=predictions['dates'], | |
| y=predictions['values'], | |
| name='AI Prediction', | |
| line=dict(color='red', width=2, dash='dash') | |
| ) | |
| ) | |
| # Confidence interval (simple) | |
| pred_std = np.std(predictions['values']) | |
| upper_band = predictions['values'] + (pred_std * 1.96) | |
| lower_band = predictions['values'] - (pred_std * 1.96) | |
| fig.add_trace( | |
| go.Scatter( | |
| x=predictions['dates'], | |
| y=upper_band, | |
| name='Upper Band', | |
| line=dict(color='lightcoral', width=1), | |
| fill=None | |
| ) | |
| ) | |
| fig.add_trace( | |
| go.Scatter( | |
| x=predictions['dates'], | |
| y=lower_band, | |
| name='Lower Band', | |
| line=dict(color='lightcoral', width=1), | |
| fill='tonexty', | |
| fillcolor='rgba(255,182,193,0.2)' | |
| ) | |
| ) | |
| fig.update_layout( | |
| title=f'Price Prediction - Next {len(predictions["dates"])} Days', | |
| xaxis_title='Date', | |
| yaxis_title='Price (IDR)', | |
| hovermode='x unified', | |
| height=500 | |
| ) | |
| return fig |