import streamlit as st import yfinance as yf import numpy as np import pandas as pd import matplotlib.pyplot as plt import scipy.optimize as sco def get_stock_data(tickers, start, end): data = yf.download(tickers, start=start, end=end) if data.empty: st.error("Data saham tidak ditemukan. Periksa ticker atau rentang tanggal.") return None if 'Adj Close' in data.columns: return data['Adj Close'] elif 'Close' in data.columns: st.warning("Menggunakan 'Close' karena 'Adj Close' tidak tersedia.") return data['Close'] else: st.error("Data harga penutupan tidak ditemukan.") return None def calculate_returns(data): log_returns = np.log(data / data.shift(1)) return log_returns.mean() * 252, log_returns.cov() * 252 def optimize_portfolio(returns, cov_matrix): num_assets = len(returns) def sharpe_ratio(weights): portfolio_return = np.dot(weights, returns) portfolio_volatility = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights))) return -portfolio_return / portfolio_volatility constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1}) bounds = tuple((0, 1) for _ in range(num_assets)) init_guess = num_assets * [1. / num_assets] result = sco.minimize(sharpe_ratio, init_guess, method='SLSQP', bounds=bounds, constraints=constraints) return result.x if result.success else None def generate_efficient_frontier(returns, cov_matrix, num_portfolios=5000): num_assets = len(returns) results = np.zeros((3, num_portfolios)) for i in range(num_portfolios): weights = np.random.dirichlet(np.ones(num_assets), size=1)[0] portfolio_return = np.dot(weights, returns) portfolio_volatility = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights))) sharpe_ratio = portfolio_return / portfolio_volatility results[0, i] = portfolio_return results[1, i] = portfolio_volatility results[2, i] = sharpe_ratio return results st.title("Analisis Portofolio Saham Optimal (Model Markowitz)") st.markdown(""" ### Teori Markowitz Model Markowitz, atau Modern Portfolio Theory (MPT), digunakan untuk membangun portofolio investasi optimal dengan memaksimalkan return untuk tingkat risiko tertentu. Portofolio yang optimal ditemukan dengan menghitung kombinasi terbaik dari aset yang tersedia untuk meminimalkan risiko dan memaksimalkan return. """) def get_recommended_stocks(): return "KLBF.JK, SIDO.JK, KAEF.JK, TLKM.JK, UNVR.JK" def validate_tickers(tickers): invalid_tickers = [t for t in tickers if yf.Ticker(t).history(period='1d').empty] if invalid_tickers: st.warning(f"Ticker tidak valid atau tidak memiliki data: {', '.join(invalid_tickers)}") return False return True st.write("Rekomendasi Saham yang Bertahan Saat COVID-19:") st.write(get_recommended_stocks()) tickers_list = st.text_input("Masukkan ticker saham", "KLBF.JK, SIDO.JK, KAEF.JK").split(", ") start_date = st.date_input("Pilih tanggal mulai", pd.to_datetime("2020-01-01")) end_date = st.date_input("Pilih tanggal akhir", pd.to_datetime("2023-12-31")) if st.button("Analisis Portofolio"): if validate_tickers(tickers_list): stock_data = get_stock_data(tickers_list, start_date, end_date) if stock_data is not None: mean_returns, cov_matrix = calculate_returns(stock_data) optimal_weights = optimize_portfolio(mean_returns, cov_matrix) st.subheader("Statistik Saham") st.write(stock_data.describe()) if optimal_weights is not None: st.subheader("Bobot Portofolio Optimal") portfolio_weights = {stock: weight for stock, weight in zip(stock_data.columns, optimal_weights)} st.write(portfolio_weights) # Pie Chart dengan filter saham dengan bobot signifikan filtered_weights = {k: v for k, v in portfolio_weights.items() if v > 0.01} fig, ax = plt.subplots() ax.pie(filtered_weights.values(), labels=filtered_weights.keys(), autopct='%1.1f%%', startangle=140) ax.axis('equal') st.pyplot(fig) st.markdown(""" **Interpretasi Pie Chart:** - Pie chart ini menunjukkan distribusi bobot optimal dari setiap saham dalam portofolio. - Semakin besar persentase suatu saham, semakin besar porsi dana yang dialokasikan ke saham tersebut dalam portofolio optimal. - Saham dengan bobot nol berarti tidak termasuk dalam portofolio optimal karena tidak memberikan kombinasi return dan risiko yang menguntungkan. - Portofolio optimal didasarkan pada perhitungan rasio Sharpe, yang mencari keseimbangan terbaik antara return dan risiko. """) # Efficient Frontier results = generate_efficient_frontier(mean_returns, cov_matrix) st.subheader("Efficient Frontier") fig, ax = plt.subplots() scatter = ax.scatter(results[1, :], results[0, :], c=results[2, :], cmap="viridis", marker='o') ax.set_xlabel("Risiko (Standar Deviasi)") ax.set_ylabel("Return Tahunan") ax.set_title("Efficient Frontier") fig.colorbar(scatter, label="Sharpe Ratio") st.pyplot(fig) st.markdown(""" **Interpretasi:** - **Bobot Portofolio Optimal**: Menunjukkan alokasi dana ke setiap saham. Saham dengan bobot nol berarti tidak termasuk dalam portofolio optimal. - **Grafik Efficient Frontier**: - Sumbu **X**: Risiko (diukur dengan standar deviasi return portofolio). - Sumbu **Y**: Return tahunan dari berbagai kombinasi portofolio. - Warna titik menunjukkan **Sharpe Ratio** – semakin terang warna, semakin optimal kombinasi risiko dan return. - **Pemilihan Portofolio Terbaik**: Portofolio di frontier efisien memberikan return terbaik untuk tingkat risiko tertentu. """) else: st.error("Optimasi portofolio gagal. Coba dengan saham yang berbeda.")